@dbml/connector 11.0.0-alpha.2 → 11.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { Client } from 'pg';
3
4
  import { scanDirNames } from './testHelpers';
4
5
  import { connector } from '../src/index';
5
6
 
@@ -44,4 +45,76 @@ describe('@dbml/connector', () => {
44
45
  test.each(scanDirNames(__dirname, 'connectors'))('connectors/%s', async (dirName) => {
45
46
  await runTest(path.join(__dirname, 'connectors', dirName));
46
47
  }, 10000);
48
+
49
+ test('postgres preserves composite foreign key column order', async () => {
50
+ const testDir = path.join(__dirname, 'connectors', 'postgres');
51
+ const { connection } = JSON.parse(fs.readFileSync(path.join(testDir, 'connection.json'), 'utf-8'));
52
+ const schema = `dbml_fk_order_${Math.random().toString(16).slice(2)}`;
53
+ const client = new Client({ connectionString: connection });
54
+
55
+ await client.connect();
56
+ try {
57
+ await client.query(`CREATE SCHEMA "${schema}"`);
58
+ await client.query(`
59
+ CREATE TABLE "${schema}".parent (
60
+ zeta integer NOT NULL,
61
+ alpha integer NOT NULL,
62
+ PRIMARY KEY (zeta, alpha)
63
+ );
64
+ CREATE TABLE "${schema}".child (
65
+ id integer PRIMARY KEY,
66
+ a_zeta integer,
67
+ z_alpha integer,
68
+ CONSTRAINT child_parent_fk FOREIGN KEY (a_zeta, z_alpha)
69
+ REFERENCES "${schema}".parent (zeta, alpha)
70
+ );
71
+ `);
72
+
73
+ const schemaJson = await connector.fetchSchemaJson(`${connection}?schemas=${schema}`, 'postgres');
74
+ const ref = schemaJson.refs.find(({ name }) => name === 'child_parent_fk');
75
+
76
+ expect(ref?.endpoints[0].fieldNames).toEqual(['a_zeta', 'z_alpha']);
77
+ expect(ref?.endpoints[1].fieldNames).toEqual(['zeta', 'alpha']);
78
+ } finally {
79
+ await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
80
+ await client.end();
81
+ }
82
+ });
83
+
84
+ test('postgres keeps array column defaults that are not an ARRAY[...] list', async () => {
85
+ const testDir = path.join(__dirname, 'connectors', 'postgres');
86
+ const { connection } = JSON.parse(fs.readFileSync(path.join(testDir, 'connection.json'), 'utf-8'));
87
+ const schema = `dbml_array_default_${Math.random().toString(16).slice(2)}`;
88
+ const client = new Client({ connectionString: connection });
89
+
90
+ await client.connect();
91
+ try {
92
+ await client.query(`CREATE SCHEMA "${schema}"`);
93
+ await client.query(`
94
+ CREATE TABLE "${schema}".t (
95
+ literal_col varchar[] DEFAULT '{full_name,date_of_birth}',
96
+ empty_literal_col text[] DEFAULT '{}',
97
+ int_literal_col integer[] DEFAULT '{1,2}',
98
+ empty_array_col text[] DEFAULT ARRAY[]::text[],
99
+ function_col integer[] DEFAULT array_fill(0, ARRAY[3]),
100
+ array_col text[] DEFAULT ARRAY['a', 'b']
101
+ );
102
+ `);
103
+
104
+ const schemaJson = await connector.fetchSchemaJson(`${connection}?schemas=${schema}`, 'postgres');
105
+ const dbdefaults = Object.fromEntries(schemaJson.fields[`${schema}.t`].map(({ name, dbdefault }) => [name, dbdefault]));
106
+
107
+ expect(dbdefaults).toEqual({
108
+ literal_col: { type: 'string', value: '{full_name,date_of_birth}' },
109
+ empty_literal_col: { type: 'string', value: '{}' },
110
+ int_literal_col: { type: 'string', value: '{1,2}' },
111
+ empty_array_col: { type: 'expression', value: 'ARRAY[]::text[]' },
112
+ function_col: { type: 'expression', value: 'array_fill(0, ARRAY[3])' },
113
+ array_col: { type: 'expression', value: 'ARRAY[\'a\', \'b\']' },
114
+ });
115
+ } finally {
116
+ await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
117
+ await client.end();
118
+ }
119
+ });
47
120
  });
package/dist/index.js CHANGED
@@ -3484,7 +3484,7 @@ sSi6
3484
3484
  ON obj.OBJECT_NAME = cols.TABLE_NAME
3485
3485
  AND obj.OBJECT_TYPE = 'TABLE'
3486
3486
  ORDER BY obj.CREATED, obj.OBJECT_ID, cols.COLUMN_ID
3487
- `,[],Xr)).rows?.reduce((e,n)=>{let{table_name:r,table_comment:i}=n;e[r]||(e[r]={name:r,schemaName:``,note:i?{value:i}:{value:``}}),t[r]||(t[r]=[]);let a=ri(n);return t[r].push(a),e},{})||{};return{tables:Object.values(n),fields:t}}function ni(e){if(e==null)return`expression`;let t=e.trim();return/^'.*'$/.test(t)?`string`:/^[0-9]+$/.test(t)||/^[0-9]*\.[0-9]*$/.test(t)?`number`:`expression`}function ri(e){let{column_name:t,data_type:n,data_length:r,data_precision:i,data_scale:a,identity_increment:o,is_nullable:s,data_default:c,column_comment:l}=e,u=ni(c),d=c&&!o?ii(c,u):null;return{name:t,type:{type_name:mi(n,r,i,a),schemaName:null},dbdefault:d,not_null:!s,increment:!!o,note:l?{value:l}:{value:``}}}function ii(e,t){return e===null?null:t===`string`?{value:e.trim().slice(1,-1).replaceAll(`''`,`'`),type:t}:{value:e,type:t}}var ai=new Set([`char`,`varchar2`,`nchar`,`nvarchar2`,`raw`,`urowid`,`character varying`,`character`,`char varying`,`nchar varying`,`varchar`,`national character varying`,`national character`,`national char varying`,`national char`]),oi=new Set([`float`]),si=`timestamp with local time zone`,ci=`timestamp with time zone`,li=`timestamp`,ui=`interval year to month`,di=`interval day to second`,fi=new Set([`number`,`numeric`,`decimal`,`dec`]);function pi(e){let t=e.split(` `).filter(Boolean).join(` `).toLowerCase();switch(t){case`anydata`:return`AnyData`;case`anytype`:return`AnyType`;case`anydataset`:return`AnyDataSet`;case`xmltype`:return`XMLType`;case`uritype`:return`URIType`;case`sdo_geometry`:return`SDO_Geometry`;case`sdo_topo_geometry`:return`SDO_Topo_Geometry`;case`sdo_georaster`:return`SDO_GeoRaster`;default:return t}}function mi(e,t,n,r){let i=pi(e);return i===si?r===null?i:`timestamp (${r}) with local time zone`:i===ci?r===null?i:`timestamp (${r}) with time zone`:i===li?r===null?i:`timestamp (${r})`:i===ui?n===null?i:`interval year (${n}) to month`:i===di?n!==null&&r!==null?`interval day (${n}) to second (${r})`:n===null?r===null?i:`interval day to second (${r})`:`interval day (${n}) to second`:ai.has(i)?t?`${i}(${t})`:i:oi.has(i)?n===null?i:`${i}(${n})`:fi.has(i)?n!==null&&r!==null?`${i}(${n}, ${r})`:n===null?i:`${i}(${n})`:i}var hi=async(e,t,n)=>{let r=await(0,h.getConnection)({username:e,password:t,connectionString:n});try{return await r.execute(`SELECT 1 FROM DUAL`),r}catch(e){throw await r.close(),e instanceof Error?Error(`OracleSQL connection error: ${e.message}`):e}};async function gi(e){let{username:t,password:n,dbidentifier:r}=Zr(e),i=await hi(t,n,r),a=ti(i),o=Qr(i),s=ei(i),c=$r(i),l=await Promise.all([a,o,s,c]),{tables:u,fields:d}=l[0],{indexes:f,tableConstraints:p,checks:m}=l[1],h=l[2],g=l[3];return Object.keys(f).forEach(e=>{g[e]?g[e].push(...f[e]):g[e]=f[e]}),await i.close(),{tables:u,fields:d,refs:h,enums:[],indexes:g,tableConstraints:p,checks:m}}var _i=async e=>{let t=new g.Client(e);try{return await t.connect(),await t.query(`SELECT 1`),t}catch(e){throw await t.end(),e instanceof Error?Error(`PostgreSQL connection error: ${e}`):e}},vi=e=>e===`YES`,yi=(e,t,n,r,i)=>e===`ARRAY`?`${t.slice(1,t.length)}[]`:n?`${t}(${n})`:r&&i?`${t}(${r},${i})`:t,bi=(e,t,n)=>{if(e===`ARRAY`)return{type:n,value:`ARRAY[${t.slice(6,-1).split(`,`).map(e=>e.split(`::`)[0]).join(`, `)}]`};if(n===`string`){let n=t.split(`::`)[0];return{type:e===`json`||e===`jsonb`?`expression`:`string`,value:n.slice(1,-1)}}return{type:n,value:t}},xi=e=>{let{column_name:t,data_type:n,character_maximum_length:r,numeric_precision:i,numeric_scale:a,udt_schema:o,udt_name:s,identity_increment:c,is_nullable:l,column_default:u,default_type:d,column_comment:f}=e,p=u&&d!==`increment`?bi(n,u,d):null;return{name:t,type:n===`USER-DEFINED`?{type_name:s,schemaName:o}:{type_name:yi(n,s,r,i,a),schemaName:null},dbdefault:p,not_null:!vi(l),increment:!!c||d===`increment`,note:f?{value:f}:{value:``}}},Si=async(e,t)=>{let n={},r=`
3487
+ `,[],Xr)).rows?.reduce((e,n)=>{let{table_name:r,table_comment:i}=n;e[r]||(e[r]={name:r,schemaName:``,note:i?{value:i}:{value:``}}),t[r]||(t[r]=[]);let a=ri(n);return t[r].push(a),e},{})||{};return{tables:Object.values(n),fields:t}}function ni(e){if(e==null)return`expression`;let t=e.trim();return/^'.*'$/.test(t)?`string`:/^[0-9]+$/.test(t)||/^[0-9]*\.[0-9]*$/.test(t)?`number`:`expression`}function ri(e){let{column_name:t,data_type:n,data_length:r,data_precision:i,data_scale:a,identity_increment:o,is_nullable:s,data_default:c,column_comment:l}=e,u=ni(c),d=c&&!o?ii(c,u):null;return{name:t,type:{type_name:mi(n,r,i,a),schemaName:null},dbdefault:d,not_null:!s,increment:!!o,note:l?{value:l}:{value:``}}}function ii(e,t){return e===null?null:t===`string`?{value:e.trim().slice(1,-1).replaceAll(`''`,`'`),type:t}:{value:e,type:t}}var ai=new Set([`char`,`varchar2`,`nchar`,`nvarchar2`,`raw`,`urowid`,`character varying`,`character`,`char varying`,`nchar varying`,`varchar`,`national character varying`,`national character`,`national char varying`,`national char`]),oi=new Set([`float`]),si=`timestamp with local time zone`,ci=`timestamp with time zone`,li=`timestamp`,ui=`interval year to month`,di=`interval day to second`,fi=new Set([`number`,`numeric`,`decimal`,`dec`]);function pi(e){let t=e.split(` `).filter(Boolean).join(` `).toLowerCase();switch(t){case`anydata`:return`AnyData`;case`anytype`:return`AnyType`;case`anydataset`:return`AnyDataSet`;case`xmltype`:return`XMLType`;case`uritype`:return`URIType`;case`sdo_geometry`:return`SDO_Geometry`;case`sdo_topo_geometry`:return`SDO_Topo_Geometry`;case`sdo_georaster`:return`SDO_GeoRaster`;default:return t}}function mi(e,t,n,r){let i=pi(e);return i===si?r===null?i:`timestamp (${r}) with local time zone`:i===ci?r===null?i:`timestamp (${r}) with time zone`:i===li?r===null?i:`timestamp (${r})`:i===ui?n===null?i:`interval year (${n}) to month`:i===di?n!==null&&r!==null?`interval day (${n}) to second (${r})`:n===null?r===null?i:`interval day to second (${r})`:`interval day (${n}) to second`:ai.has(i)?t?`${i}(${t})`:i:oi.has(i)?n===null?i:`${i}(${n})`:fi.has(i)?n!==null&&r!==null?`${i}(${n}, ${r})`:n===null?i:`${i}(${n})`:i}var hi=async(e,t,n)=>{let r=await(0,h.getConnection)({username:e,password:t,connectionString:n});try{return await r.execute(`SELECT 1 FROM DUAL`),r}catch(e){throw await r.close(),e instanceof Error?Error(`OracleSQL connection error: ${e.message}`):e}};async function gi(e){let{username:t,password:n,dbidentifier:r}=Zr(e),i=await hi(t,n,r),a=ti(i),o=Qr(i),s=ei(i),c=$r(i),l=await Promise.all([a,o,s,c]),{tables:u,fields:d}=l[0],{indexes:f,tableConstraints:p,checks:m}=l[1],h=l[2],g=l[3];return Object.keys(f).forEach(e=>{g[e]?g[e].push(...f[e]):g[e]=f[e]}),await i.close(),{tables:u,fields:d,refs:h,enums:[],indexes:g,tableConstraints:p,checks:m}}var _i=async e=>{let t=new g.Client(e);try{return await t.connect(),await t.query(`SELECT 1`),t}catch(e){throw await t.end(),e instanceof Error?Error(`PostgreSQL connection error: ${e}`):e}},vi=e=>e===`YES`,yi=(e,t,n,r,i)=>e===`ARRAY`?`${t.slice(1,t.length)}[]`:n?`${t}(${n})`:r&&i?`${t}(${r},${i})`:t,bi=(e,t,n)=>{if(e===`ARRAY`&&t.startsWith(`ARRAY[`)&&!t.startsWith(`ARRAY[]`))return{type:n,value:`ARRAY[${t.slice(6,-1).split(`,`).map(e=>e.split(`::`)[0]).join(`, `)}]`};if(n===`string`){let n=t.split(`::`)[0];return{type:e===`json`||e===`jsonb`?`expression`:`string`,value:n.slice(1,-1)}}return{type:n,value:t}},xi=e=>{let{column_name:t,data_type:n,character_maximum_length:r,numeric_precision:i,numeric_scale:a,udt_schema:o,udt_name:s,identity_increment:c,is_nullable:l,column_default:u,default_type:d,column_comment:f}=e,p=u&&d!==`increment`?bi(n,u,d):null;return{name:t,type:n===`USER-DEFINED`?{type_name:s,schemaName:o}:{type_name:yi(n,s,r,i,a),schemaName:null},dbdefault:p,not_null:!vi(l),increment:!!c||d===`increment`,note:f?{value:f}:{value:``}}},Si=async(e,t)=>{let n={},r=`
3488
3488
  WITH
3489
3489
  comments AS (
3490
3490
  SELECT DISTINCT ON (pc.relname, pn.nspname, pa.attname)
@@ -3560,40 +3560,59 @@ sSi6
3560
3560
  ;
3561
3561
  `,i=(await e.query(r)).rows.reduce((e,t)=>{let{table_schema:r,table_name:i,table_comment:a}=t,o=`${r}.${i}`;e[o]||(e[o]={name:i,schemaName:r,note:a?{value:a}:{value:``}}),n[o]||(n[o]=[]);let s=xi(t);return n[o].push(s),e},{});return{tables:Object.values(i),fields:n}},Ci=async(e,t)=>{let n=[],r=`
3562
3562
  SELECT
3563
- tc.table_schema,
3564
- tc.table_name,
3565
- tc.constraint_name as fk_constraint_name,
3566
- STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
3567
- ccu.table_schema AS foreign_table_schema,
3568
- ccu.table_name AS foreign_table_name,
3569
- STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
3570
- tc.constraint_type,
3571
- rc.delete_rule AS on_delete,
3572
- rc.update_rule AS on_update
3573
- FROM information_schema.table_constraints AS tc
3574
- JOIN information_schema.key_column_usage AS kcu
3575
- ON tc.constraint_name = kcu.constraint_name
3576
- AND tc.table_schema = kcu.table_schema
3577
- JOIN information_schema.constraint_column_usage AS ccu
3578
- ON ccu.constraint_name = tc.constraint_name
3579
- JOIN information_schema.referential_constraints AS rc
3580
- ON tc.constraint_name = rc.constraint_name
3581
- AND tc.table_schema = rc.constraint_schema
3582
- WHERE tc.constraint_type = 'FOREIGN KEY'
3583
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
3584
- ${j(`tc.table_schema`,t)}
3563
+ source_ns.nspname AS table_schema,
3564
+ source_table.relname AS table_name,
3565
+ constraint_row.conname AS fk_constraint_name,
3566
+ STRING_AGG(source_column.attname, ',' ORDER BY key_column.ordinality) AS column_names,
3567
+ target_ns.nspname AS foreign_table_schema,
3568
+ target_table.relname AS foreign_table_name,
3569
+ STRING_AGG(target_column.attname, ',' ORDER BY key_column.ordinality) AS foreign_column_names,
3570
+ CASE constraint_row.confdeltype
3571
+ WHEN 'a' THEN 'NO ACTION'
3572
+ WHEN 'r' THEN 'RESTRICT'
3573
+ WHEN 'c' THEN 'CASCADE'
3574
+ WHEN 'n' THEN 'SET NULL'
3575
+ WHEN 'd' THEN 'SET DEFAULT'
3576
+ END AS on_delete,
3577
+ CASE constraint_row.confupdtype
3578
+ WHEN 'a' THEN 'NO ACTION'
3579
+ WHEN 'r' THEN 'RESTRICT'
3580
+ WHEN 'c' THEN 'CASCADE'
3581
+ WHEN 'n' THEN 'SET NULL'
3582
+ WHEN 'd' THEN 'SET DEFAULT'
3583
+ END AS on_update
3584
+ FROM pg_constraint AS constraint_row
3585
+ JOIN pg_class AS source_table
3586
+ ON constraint_row.conrelid = source_table.oid
3587
+ JOIN pg_namespace AS source_ns
3588
+ ON source_table.relnamespace = source_ns.oid
3589
+ JOIN pg_class AS target_table
3590
+ ON constraint_row.confrelid = target_table.oid
3591
+ JOIN pg_namespace AS target_ns
3592
+ ON target_table.relnamespace = target_ns.oid
3593
+ JOIN LATERAL unnest(constraint_row.conkey, constraint_row.confkey)
3594
+ WITH ORDINALITY AS key_column(source_attnum, target_attnum, ordinality)
3595
+ ON TRUE
3596
+ JOIN pg_attribute AS source_column
3597
+ ON source_column.attrelid = source_table.oid
3598
+ AND source_column.attnum = key_column.source_attnum
3599
+ JOIN pg_attribute AS target_column
3600
+ ON target_column.attrelid = target_table.oid
3601
+ AND target_column.attnum = key_column.target_attnum
3602
+ WHERE constraint_row.contype = 'f'
3603
+ AND source_ns.nspname NOT IN ('pg_catalog', 'information_schema')
3604
+ ${j(`source_ns.nspname`,t)}
3585
3605
  GROUP BY
3586
- tc.table_schema,
3587
- tc.table_name,
3588
- tc.constraint_name,
3589
- ccu.table_schema,
3590
- ccu.table_name,
3591
- tc.constraint_type,
3592
- rc.delete_rule,
3593
- rc.update_rule
3606
+ source_ns.nspname,
3607
+ source_table.relname,
3608
+ constraint_row.conname,
3609
+ target_ns.nspname,
3610
+ target_table.relname,
3611
+ constraint_row.confdeltype,
3612
+ constraint_row.confupdtype
3594
3613
  ORDER BY
3595
- tc.table_schema,
3596
- tc.table_name;
3614
+ source_ns.nspname,
3615
+ source_table.relname;
3597
3616
  `;return(await e.query(r)).rows.forEach(e=>{let{table_schema:t,fk_constraint_name:r,table_name:i,column_names:a,foreign_table_schema:o,foreign_table_name:s,foreign_column_names:c,on_delete:l,on_update:u}=e,d={tableName:i,schemaName:t,fieldNames:a.split(`,`),relation:`0..*`},f={tableName:s,schemaName:o,fieldNames:c.split(`,`),relation:`0..1`};n.push({name:r,endpoints:[d,f],onDelete:l===`NO ACTION`?null:l,onUpdate:u===`NO ACTION`?null:u})}),n},wi=async(e,t)=>{let n=`
3598
3617
  WITH user_tables AS (
3599
3618
  SELECT
package/dist/index.mjs CHANGED
@@ -9886,7 +9886,7 @@ var gi = async (e) => {
9886
9886
  throw await t.end(), e instanceof Error ? Error(`PostgreSQL connection error: ${e}`) : e;
9887
9887
  }
9888
9888
  }, _i = (e) => e === "YES", vi = (e, t, n, r, i) => e === "ARRAY" ? `${t.slice(1, t.length)}[]` : n ? `${t}(${n})` : r && i ? `${t}(${r},${i})` : t, yi = (e, t, n) => {
9889
- if (e === "ARRAY") return {
9889
+ if (e === "ARRAY" && t.startsWith("ARRAY[") && !t.startsWith("ARRAY[]")) return {
9890
9890
  type: n,
9891
9891
  value: `ARRAY[${t.slice(6, -1).split(",").map((e) => e.split("::")[0]).join(", ")}]`
9892
9892
  };
@@ -10009,40 +10009,59 @@ var gi = async (e) => {
10009
10009
  }, Si = async (e, t) => {
10010
10010
  let n = [], r = `
10011
10011
  SELECT
10012
- tc.table_schema,
10013
- tc.table_name,
10014
- tc.constraint_name as fk_constraint_name,
10015
- STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
10016
- ccu.table_schema AS foreign_table_schema,
10017
- ccu.table_name AS foreign_table_name,
10018
- STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
10019
- tc.constraint_type,
10020
- rc.delete_rule AS on_delete,
10021
- rc.update_rule AS on_update
10022
- FROM information_schema.table_constraints AS tc
10023
- JOIN information_schema.key_column_usage AS kcu
10024
- ON tc.constraint_name = kcu.constraint_name
10025
- AND tc.table_schema = kcu.table_schema
10026
- JOIN information_schema.constraint_column_usage AS ccu
10027
- ON ccu.constraint_name = tc.constraint_name
10028
- JOIN information_schema.referential_constraints AS rc
10029
- ON tc.constraint_name = rc.constraint_name
10030
- AND tc.table_schema = rc.constraint_schema
10031
- WHERE tc.constraint_type = 'FOREIGN KEY'
10032
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
10033
- ${A("tc.table_schema", t)}
10012
+ source_ns.nspname AS table_schema,
10013
+ source_table.relname AS table_name,
10014
+ constraint_row.conname AS fk_constraint_name,
10015
+ STRING_AGG(source_column.attname, ',' ORDER BY key_column.ordinality) AS column_names,
10016
+ target_ns.nspname AS foreign_table_schema,
10017
+ target_table.relname AS foreign_table_name,
10018
+ STRING_AGG(target_column.attname, ',' ORDER BY key_column.ordinality) AS foreign_column_names,
10019
+ CASE constraint_row.confdeltype
10020
+ WHEN 'a' THEN 'NO ACTION'
10021
+ WHEN 'r' THEN 'RESTRICT'
10022
+ WHEN 'c' THEN 'CASCADE'
10023
+ WHEN 'n' THEN 'SET NULL'
10024
+ WHEN 'd' THEN 'SET DEFAULT'
10025
+ END AS on_delete,
10026
+ CASE constraint_row.confupdtype
10027
+ WHEN 'a' THEN 'NO ACTION'
10028
+ WHEN 'r' THEN 'RESTRICT'
10029
+ WHEN 'c' THEN 'CASCADE'
10030
+ WHEN 'n' THEN 'SET NULL'
10031
+ WHEN 'd' THEN 'SET DEFAULT'
10032
+ END AS on_update
10033
+ FROM pg_constraint AS constraint_row
10034
+ JOIN pg_class AS source_table
10035
+ ON constraint_row.conrelid = source_table.oid
10036
+ JOIN pg_namespace AS source_ns
10037
+ ON source_table.relnamespace = source_ns.oid
10038
+ JOIN pg_class AS target_table
10039
+ ON constraint_row.confrelid = target_table.oid
10040
+ JOIN pg_namespace AS target_ns
10041
+ ON target_table.relnamespace = target_ns.oid
10042
+ JOIN LATERAL unnest(constraint_row.conkey, constraint_row.confkey)
10043
+ WITH ORDINALITY AS key_column(source_attnum, target_attnum, ordinality)
10044
+ ON TRUE
10045
+ JOIN pg_attribute AS source_column
10046
+ ON source_column.attrelid = source_table.oid
10047
+ AND source_column.attnum = key_column.source_attnum
10048
+ JOIN pg_attribute AS target_column
10049
+ ON target_column.attrelid = target_table.oid
10050
+ AND target_column.attnum = key_column.target_attnum
10051
+ WHERE constraint_row.contype = 'f'
10052
+ AND source_ns.nspname NOT IN ('pg_catalog', 'information_schema')
10053
+ ${A("source_ns.nspname", t)}
10034
10054
  GROUP BY
10035
- tc.table_schema,
10036
- tc.table_name,
10037
- tc.constraint_name,
10038
- ccu.table_schema,
10039
- ccu.table_name,
10040
- tc.constraint_type,
10041
- rc.delete_rule,
10042
- rc.update_rule
10055
+ source_ns.nspname,
10056
+ source_table.relname,
10057
+ constraint_row.conname,
10058
+ target_ns.nspname,
10059
+ target_table.relname,
10060
+ constraint_row.confdeltype,
10061
+ constraint_row.confupdtype
10043
10062
  ORDER BY
10044
- tc.table_schema,
10045
- tc.table_name;
10063
+ source_ns.nspname,
10064
+ source_table.relname;
10046
10065
  `;
10047
10066
  return (await e.query(r)).rows.forEach((e) => {
10048
10067
  let { table_schema: t, fk_constraint_name: r, table_name: i, column_names: a, foreign_table_schema: o, foreign_table_name: s, foreign_column_names: c, on_delete: l, on_update: u } = e, d = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@dbml/connector",
4
- "version": "11.0.0-alpha.2",
4
+ "version": "11.0.0-alpha.4",
5
5
  "description": "This package was created to fetch the schema JSON from many kind of databases.",
6
6
  "author": "huy.phung.sw@gmail.com",
7
7
  "license": "MIT",
@@ -39,5 +39,5 @@
39
39
  "engines": {
40
40
  "node": ">=18"
41
41
  },
42
- "gitHead": "75a0e5bf55149fef937ec7d02e48c170421229ff"
42
+ "gitHead": "40f71a2018dd2a5a9452cd4ba680ef08289fae3a"
43
43
  }
@@ -135,7 +135,7 @@ function getDbdefault (columnDefault: string | null, defaultType: string): Defau
135
135
 
136
136
  // Field type utils
137
137
 
138
- // These datatypes are never qualified: data size, data scale and data precision are all ignore.
138
+ // These datatypes are never qualified: data size, data scale and data precision
139
139
  // Ref: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
140
140
  const UNQUALIFIED_DATA_TYPES = new Set([
141
141
  // datetime
@@ -54,7 +54,9 @@ const getFieldType = (data_type: string, udt_name: string, character_maximum_len
54
54
  };
55
55
 
56
56
  const getDbdefault = (data_type: string, column_default: string, default_type: DefaultType): DefaultInfo => {
57
- if (data_type === 'ARRAY') {
57
+ // Only a non-empty ARRAY[...] constructor is rewritten here. Other array defaults, such as
58
+ // '{a,b}'::text[], ARRAY[]::text[] or a function call, are handled below like any other default
59
+ if (data_type === 'ARRAY' && column_default.startsWith('ARRAY[') && !column_default.startsWith('ARRAY[]')) {
58
60
  const values = column_default.slice(6, -1).split(',').map((value) => {
59
61
  return value.split('::')[0];
60
62
  });
@@ -228,40 +230,59 @@ const generateRawRefs = async (client: Client, schemas: string[]): Promise<Ref[]
228
230
 
229
231
  const refsListSql = `
230
232
  SELECT
231
- tc.table_schema,
232
- tc.table_name,
233
- tc.constraint_name as fk_constraint_name,
234
- STRING_AGG(DISTINCT kcu.column_name, ',') AS column_names,
235
- ccu.table_schema AS foreign_table_schema,
236
- ccu.table_name AS foreign_table_name,
237
- STRING_AGG(DISTINCT ccu.column_name, ',') AS foreign_column_names,
238
- tc.constraint_type,
239
- rc.delete_rule AS on_delete,
240
- rc.update_rule AS on_update
241
- FROM information_schema.table_constraints AS tc
242
- JOIN information_schema.key_column_usage AS kcu
243
- ON tc.constraint_name = kcu.constraint_name
244
- AND tc.table_schema = kcu.table_schema
245
- JOIN information_schema.constraint_column_usage AS ccu
246
- ON ccu.constraint_name = tc.constraint_name
247
- JOIN information_schema.referential_constraints AS rc
248
- ON tc.constraint_name = rc.constraint_name
249
- AND tc.table_schema = rc.constraint_schema
250
- WHERE tc.constraint_type = 'FOREIGN KEY'
251
- AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
252
- ${buildSchemaQuery('tc.table_schema', schemas)}
233
+ source_ns.nspname AS table_schema,
234
+ source_table.relname AS table_name,
235
+ constraint_row.conname AS fk_constraint_name,
236
+ STRING_AGG(source_column.attname, ',' ORDER BY key_column.ordinality) AS column_names,
237
+ target_ns.nspname AS foreign_table_schema,
238
+ target_table.relname AS foreign_table_name,
239
+ STRING_AGG(target_column.attname, ',' ORDER BY key_column.ordinality) AS foreign_column_names,
240
+ CASE constraint_row.confdeltype
241
+ WHEN 'a' THEN 'NO ACTION'
242
+ WHEN 'r' THEN 'RESTRICT'
243
+ WHEN 'c' THEN 'CASCADE'
244
+ WHEN 'n' THEN 'SET NULL'
245
+ WHEN 'd' THEN 'SET DEFAULT'
246
+ END AS on_delete,
247
+ CASE constraint_row.confupdtype
248
+ WHEN 'a' THEN 'NO ACTION'
249
+ WHEN 'r' THEN 'RESTRICT'
250
+ WHEN 'c' THEN 'CASCADE'
251
+ WHEN 'n' THEN 'SET NULL'
252
+ WHEN 'd' THEN 'SET DEFAULT'
253
+ END AS on_update
254
+ FROM pg_constraint AS constraint_row
255
+ JOIN pg_class AS source_table
256
+ ON constraint_row.conrelid = source_table.oid
257
+ JOIN pg_namespace AS source_ns
258
+ ON source_table.relnamespace = source_ns.oid
259
+ JOIN pg_class AS target_table
260
+ ON constraint_row.confrelid = target_table.oid
261
+ JOIN pg_namespace AS target_ns
262
+ ON target_table.relnamespace = target_ns.oid
263
+ JOIN LATERAL unnest(constraint_row.conkey, constraint_row.confkey)
264
+ WITH ORDINALITY AS key_column(source_attnum, target_attnum, ordinality)
265
+ ON TRUE
266
+ JOIN pg_attribute AS source_column
267
+ ON source_column.attrelid = source_table.oid
268
+ AND source_column.attnum = key_column.source_attnum
269
+ JOIN pg_attribute AS target_column
270
+ ON target_column.attrelid = target_table.oid
271
+ AND target_column.attnum = key_column.target_attnum
272
+ WHERE constraint_row.contype = 'f'
273
+ AND source_ns.nspname NOT IN ('pg_catalog', 'information_schema')
274
+ ${buildSchemaQuery('source_ns.nspname', schemas)}
253
275
  GROUP BY
254
- tc.table_schema,
255
- tc.table_name,
256
- tc.constraint_name,
257
- ccu.table_schema,
258
- ccu.table_name,
259
- tc.constraint_type,
260
- rc.delete_rule,
261
- rc.update_rule
276
+ source_ns.nspname,
277
+ source_table.relname,
278
+ constraint_row.conname,
279
+ target_ns.nspname,
280
+ target_table.relname,
281
+ constraint_row.confdeltype,
282
+ constraint_row.confupdtype
262
283
  ORDER BY
263
- tc.table_schema,
264
- tc.table_name;
284
+ source_ns.nspname,
285
+ source_table.relname;
265
286
  `;
266
287
 
267
288
  const refsQueryResult = await client.query(refsListSql);