Skip to content

Commit 0f9a0c4

Browse files
authored
Merge pull request #1280 from constructive-io/update/export-add-db-name-match
Added current_database() to supply the correct value at deploy time
2 parents 724e07e + c7da81c commit 0f9a0c4

5 files changed

Lines changed: 104 additions & 1 deletion

File tree

pgpm/export/__tests__/dynamic-fields.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,27 @@ describe('typeOverrides should take precedence over introspected types', () => {
322322
const field = META_TABLE_CONFIG.field;
323323
expect(field.typeOverrides).toBeUndefined();
324324
});
325+
326+
it('columnDefaults should be set for tables with environment-specific columns', () => {
327+
// apis.dbname defaults to current_database() and must not be exported
328+
// as a hardcoded literal — see constructive-db commit 348a5b402e.
329+
const apis = META_TABLE_CONFIG.apis;
330+
expect(apis.columnDefaults).toBeDefined();
331+
expect(apis.columnDefaults!.dbname).toBe('current_database()');
332+
333+
// sites.dbname has the same portability issue
334+
const sites = META_TABLE_CONFIG.sites;
335+
expect(sites.columnDefaults).toBeDefined();
336+
expect(sites.columnDefaults!.dbname).toBe('current_database()');
337+
});
338+
339+
it('tables without columnDefaults should have no columnDefaults key', () => {
340+
const database = META_TABLE_CONFIG.database;
341+
expect(database.columnDefaults).toBeUndefined();
342+
343+
const domains = META_TABLE_CONFIG.domains;
344+
expect(domains.columnDefaults).toBeUndefined();
345+
});
325346
});
326347

327348
// =============================================================================

pgpm/export/__tests__/export-flow.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,5 +730,33 @@ relocatable = false
730730
const structure = getDirectoryStructure(svcDeployDir);
731731
expect(structure).toMatchSnapshot('pets-export-svc deploy folder');
732732
});
733+
734+
// Behavioral test: columnDefaults columns must be absent from the generated SQL.
735+
// The apis and sites tables have dbname DEFAULT current_database(), which
736+
// captures an environment-specific literal during export. columnDefaults
737+
// strips the column from the INSERT so the DDL default supplies the correct
738+
// value at deploy time (constructive-db commit 348a5b402e).
739+
it('should exclude dbname from apis and sites INSERTs (columnDefaults)', () => {
740+
const apisSqlPath = join(exportWorkspaceDir, 'packages', META_EXTENSION_NAME, 'deploy', 'migrate', 'apis.sql');
741+
const sitesSqlPath = join(exportWorkspaceDir, 'packages', META_EXTENSION_NAME, 'deploy', 'migrate', 'sites.sql');
742+
743+
if (existsSync(apisSqlPath)) {
744+
const apisContent = readFileSync(apisSqlPath, 'utf-8');
745+
// dbname must NOT appear — it's stripped by columnDefaults
746+
expect(apisContent).not.toContain('dbname');
747+
// But other columns should still be present
748+
expect(apisContent).toContain('name');
749+
expect(apisContent).toContain('is_public');
750+
}
751+
752+
if (existsSync(sitesSqlPath)) {
753+
const sitesContent = readFileSync(sitesSqlPath, 'utf-8');
754+
// dbname must NOT appear — it's stripped by columnDefaults
755+
expect(sitesContent).not.toContain('dbname');
756+
// But other columns should still be present
757+
expect(sitesContent).toContain('title');
758+
expect(sitesContent).toContain('description');
759+
}
760+
});
733761
});
734762
});

pgpm/export/src/export-graphql-meta.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ const buildDynamicFieldsFromGraphQL = async (
9494
}
9595
}
9696

97+
// Omit columns that are marked as columnDefaults — their DDL DEFAULT (e.g.
98+
// current_database()) will supply the correct value at deploy time, so the
99+
// exported INSERT must not hardcode an environment-specific literal.
100+
if (tableConfig.columnDefaults) {
101+
for (const colName of Object.keys(tableConfig.columnDefaults)) {
102+
delete dynamicFields[colName];
103+
enumFields.delete(colName);
104+
}
105+
}
106+
97107
return { fields: dynamicFields, enumFields };
98108
} catch (err: unknown) {
99109
const message = err instanceof Error ? err.message : String(err);
@@ -192,6 +202,17 @@ export const exportGraphQLMeta = async ({
192202

193203
if (Object.keys(dynamicFields).length === 0) return;
194204

205+
// Omit columnDefaults columns from row data so the Parser never sees them.
206+
// configFields already excludes them (via buildDynamicFieldsFromGraphQL),
207+
// so dynamicFields won't contain them either — but the pgRow data still does.
208+
if (tableConfig.columnDefaults) {
209+
for (const colName of Object.keys(tableConfig.columnDefaults)) {
210+
for (const row of pgRows) {
211+
delete row[colName];
212+
}
213+
}
214+
}
215+
195216
const parser = new Parser({
196217
schema: tableConfig.schema,
197218
table: tableConfig.table,

pgpm/export/src/export-meta.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ const buildDynamicFields = async (
5757
}
5858
}
5959

60+
// Omit columns that are marked as columnDefaults — their DDL DEFAULT (e.g.
61+
// current_database()) will supply the correct value at deploy time, so the
62+
// exported INSERT must not hardcode an environment-specific literal.
63+
if (tableConfig.columnDefaults) {
64+
for (const colName of Object.keys(tableConfig.columnDefaults)) {
65+
delete dynamicFields[colName];
66+
}
67+
}
68+
6069
return dynamicFields;
6170
};
6271

@@ -138,6 +147,18 @@ export const exportMeta = async ({ opts, dbname, database_id }: ExportMetaParams
138147
}
139148
}
140149

150+
// Omit columnDefaults columns from row data so the Parser never sees them.
151+
// The Parser's field config already excludes them (via buildDynamicFields),
152+
// so they would be ignored anyway, but removing them from the data is cleaner.
153+
const tblCfg = META_TABLE_CONFIG[key];
154+
if (tblCfg?.columnDefaults) {
155+
for (const colName of Object.keys(tblCfg.columnDefaults)) {
156+
for (const row of result.rows) {
157+
delete row[colName];
158+
}
159+
}
160+
}
161+
141162
const parsed = await parser.parse(result.rows);
142163
if (parsed) {
143164
sql[key] = parsed;

pgpm/export/src/export-utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ export interface TableConfig {
201201
conflictDoNothing?: boolean;
202202
typeOverrides?: Record<string, FieldType>; // only for special types (image, upload, url) that can't be inferred
203203
gqlTypeName?: string; // override for GraphQL type name when automatic derivation doesn't match PostGraphile's inflector
204+
/** Columns whose values are environment-specific and should be excluded from the
205+
* exported INSERT so that the column's DDL DEFAULT applies at deploy time.
206+
* Key = column name, Value = the SQL expression the column defaults to (for documentation).
207+
* E.g. { dbname: 'current_database()' } — the exporter omits `dbname` from the
208+
* INSERT, and `DEFAULT current_database()` in the table definition supplies it. */
209+
columnDefaults?: Record<string, string>;
204210
}
205211

206212
/**
@@ -306,11 +312,17 @@ export const META_TABLE_CONFIG: Record<string, TableConfig> = {
306312
favicon: 'upload',
307313
apple_touch_icon: 'image',
308314
logo: 'image'
315+
},
316+
columnDefaults: {
317+
dbname: 'current_database()'
309318
}
310319
},
311320
apis: {
312321
schema: 'services_public',
313-
table: 'apis'
322+
table: 'apis',
323+
columnDefaults: {
324+
dbname: 'current_database()'
325+
}
314326
},
315327
apps: {
316328
schema: 'services_public',

0 commit comments

Comments
 (0)