"use strict" ;
// TODO: Make this its own independent package once the last bits have been polished (eg. consistent type names and onUpdate/onDelete values)
const assert = require ( "assert" ) ;
module . exports = async function knexIntrospect ( knex , tableName ) {
let clientType = knex . client . config . client ;
assert ( clientType === "sqlite3" || clientType === "pg" ) ;
assert ( /^[a-zA-Z_]+$/ . test ( tableName ) ) ;
let safeTableName = tableName ; // FIXME: Sanitize instead of assert?
let tableLayout = { } ;
if ( clientType === "sqlite3" ) {
let [ columns , foreignKeys ] = await Promise . all ( [
knex . raw ( ` PRAGMA table_info(' ${ safeTableName } ') ` ) ,
knex . raw ( ` PRAGMA foreign_key_list(' ${ safeTableName } ') ` )
] ) ;
for ( let column of columns ) {
tableLayout [ column . name ] = {
name : column . name ,
type : column . type ,
notNull : ( column . notnull === 1 ) ,
isPrimaryKey : ( column . pk === 1 ) ,
foreignKey : null
} ;
}
for ( let fk of foreignKeys ) {
tableLayout [ fk . from ] . foreignKey = {
table : fk . table ,
column : fk . to ,
onUpdate : fk . on _update ,
onDelete : fk . on _delete
} ;
}
} else if ( clientType === "pg" ) {
// Yes, this is a lot of queries. Yes, I "should" have used a single query with JOINs. If I did that, I would now have 1) a headache and 2) no working code. If this is something you care about, feel free to submit a PR.
let tables = await knex ( "pg_class" ) . limit ( 1 ) . where ( { relname : tableName } ) ;
assert ( tables . length === 1 ) ;
let table = tables [ 0 ] ;
let columns = await knex ( "pg_attribute" )
. where ( { attrelid : table . oid } )
. where ( "attnum" , ">" , 0 ) ;
for ( let column of columns ) {
let constraints = await knex ( "pg_constraint" )
. where ( { conrelid : table . oid } )
. whereRaw ( ` conkey = ARRAY[?::SMALLINT] ` , [ column . attnum ] ) ;
let primaryKeyConstraint , foreignKey ;
for ( let constraint of constraints ) {
if ( constraint . contype === "p" ) {
primaryKeyConstraint = constraint ;
} else if ( constraint . contype === "f" ) {
// TODO: Can there be multiple foreign key constraints?
let foreignTable = await knex ( "pg_class" ) . limit ( 1 ) . where ( { oid : constraint . confrelid } ) ;
// TODO: Support composite keys?
assert ( constraint . confkey . length === 1 ) ;
let foreignColumn = await knex ( "pg_attribute" )
. limit ( 1 )
. where ( { attrelid : constraint . confrelid } )
. whereRaw ( ` attnum = ?::SMALLINT ` , [ constraint . confkey [ 0 ] ] ) ;
foreignKey = {
table : foreignTable [ 0 ] . relname ,
column : foreignColumn [ 0 ] . attname ,
// TODO: Convert possible values for onUpdate/onDelete into something consistent with SQLite, using match-value
onUpdate : ( constraint . confupdtype === "a" ) ? null : constraint . confupdtype ,
onDelete : ( constraint . confdeltype === "a" ) ? null : constraint . confdeltype
} ;
}
}
tableLayout [ column . attname ] = {
name : column . attname ,
type : ( await knex ( "pg_type" ) . limit ( 1 ) . where ( { oid : column . atttypid } ) ) [ 0 ] . typname ,
notNull : column . attnotnull ,
isPrimaryKey : ( primaryKeyConstraint != null ) ,
foreignKey : foreignKey
} ;
}
}
return tableLayout ;
} ;