Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8faae9d
Add mssql dialect
johnsoftek Oct 26, 2018
147d109
Upgrade package versions
johnsoftek Oct 27, 2018
1cc2eac
Replace playground with graphiql
johnsoftek Oct 27, 2018
db2dc92
Upgrade to lodash 4
johnsoftek Oct 27, 2018
3855bd6
Added relationships, table and column comments for mssql dialect
johnsoftek Oct 29, 2018
733bc8c
Restore es6 server
johnsoftek Oct 30, 2018
8a38b56
Extend query model to include lists
johnsoftek Oct 31, 2018
a50e445
s
johnsoftek Nov 16, 2018
3f876a6
Remove ast builder
johnsoftek Nov 16, 2018
0dd186e
Fix config
johnsoftek Nov 17, 2018
44cbbc5
Assume id is primary key
johnsoftek Nov 17, 2018
3b192f3
Remove unnecessary packages
johnsoftek Nov 17, 2018
e3c96c6
Check point
johnsoftek Nov 18, 2018
3e162d6
Check point
johnsoftek Nov 18, 2018
5c4a343
Remove callbacks frim Interactive prompts
johnsoftek Nov 19, 2018
9d0735e
Several bug fixes
johnsoftek Nov 20, 2018
34f2010
Handle multiple references from one model to another model
johnsoftek Nov 21, 2018
aa1d875
Handle multiple references from one model to another model
johnsoftek Nov 21, 2018
f5d5149
Fix mutliple refs
johnsoftek Nov 21, 2018
7008934
Fix mutliple refs
johnsoftek Nov 21, 2018
32b139a
Merge branch 'master' of https://github.com/johnsoftek/sql-to-graphql
johnsoftek Nov 22, 2018
24be038
Fix manhy to one ref aliases
johnsoftek Nov 23, 2018
a497666
Multiple references working
johnsoftek Nov 23, 2018
3be464a
Refactor mutliple references
johnsoftek Nov 23, 2018
4b45709
Add scaffold files
johnsoftek Nov 24, 2018
0092ea8
Remove duplicate keys check
johnsoftek Nov 24, 2018
b029264
Add type, field and reference descriptions
johnsoftek Nov 24, 2018
0500f88
Reformat files using prettier
johnsoftek Nov 24, 2018
f99d23d
Simplify reference processing
johnsoftek Nov 24, 2018
b79b8c0
Disallow join field as arfument for nested types
johnsoftek Nov 24, 2018
d757a77
Remoive hasDuplicateValues from adapters
johnsoftek Nov 24, 2018
844ffca
Fix
johnsoftek Nov 25, 2018
9ce2e3b
Fix
johnsoftek Nov 27, 2018
ae37d71
Bump SQL Server version
johnsoftek Nov 27, 2018
9ed8be7
Check point
johnsoftek Nov 27, 2018
9ba340a
Remove extra fields
johnsoftek Dec 1, 2018
fc83a4b
Set node version
johnsoftek Dec 1, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
templates/public/js
steps/ast-builders/templates/node-def-es6.js
node_modules/
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10.13.0
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
*.json
node_modules/
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"semi": false,
"singleQuote": true,
"printWidth": 100
}
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Generate GraphQL schemas and server based on SQL table structure.
`sql-to-graphql` is a command-line utility that can help you get started. You give it the credentials to an SQL database (MySQL, PostgreSQL and SQLite currently) and it will inspect the tables it finds and do the following:

- Generate GraphQL-types for each table (including resolvers)
- Generate an HTTP-server based on Hapi that accepts GraphQL queries
- Sets up a basic web-frontend that lets you query the server
- Generate an HTTP-server based on Express that accepts GraphQL queries
- Exposes a graphiql playground to query the server

## Disclaimer

Expand All @@ -28,7 +28,6 @@ This utility is intended to help people get started with GraphQL. It is **NOT**

- `--relay`, `-r` - Generate Relay-style schema *`(boolean [default: false])`*
- `--output-dir`, `-o` - Directory to use when generating app *`(string [required])`*
- `--es6` - Use ES6 for generated code *`(boolean [default: false])`*
- `--database`, `--db` - Database name *`(string [required])`*
- `--db-filename` - Database filename, used for SQLite *`(string)`*
- `--host`, `-h` - Hostname of database server *`(string [default: "localhost"])`*
Expand Down
21 changes: 11 additions & 10 deletions backends/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use strict';
'use strict'

var adapters = {
mysql: require('./mysql'),
postgres: require('./postgres'),
pg: require('./postgres'),
sqlite: require('./sqlite')
};
const adapters = {
mssql: require('./mssql'),
mysql: require('./mysql'),
postgres: require('./postgres'),
pg: require('./postgres'),
sqlite: require('./sqlite')
}

module.exports = function getBackendAdapter(db) {
var backend = (db || '<not set>').toLowerCase();
return adapters[backend];
};
var backend = (db || '<not set>').toLowerCase()
return adapters[backend]
}
167 changes: 167 additions & 0 deletions backends/mssql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* eslint camelcase: 0 */
'use strict'

const knex = require('knex')
const mapKeys = require('lodash/mapKeys')
const contains = require('lodash/includes')
const camelCase = require('lodash/camelCase')
const pluck = require('lodash/map')

module.exports = function mssqlBackend(opts, callback) {
const mssql = knex({
client: 'mssql',
connection: opts
})

opts.stripPrefix = ['dbo.']

process.nextTick(callback)

return {
getTables: function(tableNames, cb) {
const matchAll = tableNames.length === 1 && tableNames[0] === '*'

// omit dbo. from table name if dbo schema selected
const tablename_expr = `table_schema + '.' + table_name`
// `case when table_schema = 'dbo' then table_name else
// table_schema + '.' + table_name end`

let sql = mssql
.select(mssql.raw(tablename_expr + ' as name'))
.from('information_schema.tables')
.where('table_type', 'BASE TABLE')
if (opts.schemas !== '*') {
sql = sql.whereIn('table_schema', opts.schemas.split(','))
}

sql.catch(cb).then(function(tbls) {
tbls = pluck(tbls, 'name')
if (!matchAll) {
tbls = tbls.filter(function(tbl) {
return contains(tableNames, tbl)
})
}
cb(null, tbls)
})
},

getTableComment: function(tableName, cb) {
// return cb(null, '')
const sql = `
select CAST(ep.value AS sql_variant) AS comment
FROM sys.tables as tbl
INNER JOIN sys.extended_properties AS ep
on tbl.object_id = ep.major_id
where tbl.object_id = object_id('${tableName}')
and ep.minor_id = 0
`
mssql
.raw(sql)
.catch(cb)
.then(function(comments) {
comments = pluck(comments, 'comment')
cb(null, comments[0])
})
},

getTableStructure: function(tableName, cb) {
var ref_tableNameExpr = `rs.name + '.' + rt.name`
// `case when rs.name = 'dbo' then rt.name else
// rs.name + '.' + rt.name end`

var sql = `with ref_cols as (
select ${ref_tableNameExpr} as ref_table_name,
fkc.parent_column_id as fk_column_id,
rc.name as ref_column_name,
count(*) over(partition by fk.name) as num_ref_cols
FROM sys.foreign_keys AS fk
inner join sys.foreign_key_columns as fkc
on fkc.constraint_object_id = fk.object_id
inner join sys.columns AS rc
ON fkc.referenced_column_id = rc.column_id
AND fkc.referenced_object_id = rc.[object_id]
INNER JOIN sys.tables AS rt -- referenced table
ON fk.referenced_object_id = rt.[object_id]
INNER JOIN sys.schemas AS rs
ON rt.[schema_id] = rs.[schema_id]
where fk.parent_object_id = object_id('${tableName}')
),
single_fk_cols as (
select *
from ref_cols
where num_ref_cols = 1
),
pk_cols as (
SELECT c.column_id,
ic.key_ordinal,
count(*) over() as num_pk_cols
FROM sys.indexes i
join sys.index_columns ic on i.object_id = ic.object_id
and i.index_id = ic.index_id
join sys.columns as c on ic.object_id = c.object_id
and ic.column_id = c.column_id
where i.object_id = object_id('${tableName}')
and i.is_primary_key = 1
),
single_pk_cols as (
select *
from pk_cols
where num_pk_cols = 1
),
col as (select c.name as column_name,
column_id,
c.is_nullable,
c.is_identity,
st.name as data_type,
CAST(ep.value AS sql_variant) as column_comment
from sys.columns as c
left join sys.extended_properties AS ep
on c.object_id = ep.major_id
and c.column_id = ep.minor_id
and ep.class_desc = 'OBJECT_OR_COLUMN'
join sys.types as st
on st.user_type_id = c.system_type_id
where c.object_id = object_id('${tableName}')
)

select '${tableName}' as [table],
col.column_name,
col.column_id as ordinal_position,
col.is_nullable,
col.is_identity as is_auto_increment,
col.data_type,
column_comment,
ref_table_name,
fk_cols.ref_column_name,
case when pk_cols.column_id is null
then null
else 'PRI'
end as columnKey
from col
left join single_fk_cols as fk_cols
on col.column_id = fk_cols.fk_column_id
left join single_pk_cols as pk_cols
on col.column_id = pk_cols.column_id
order by col.column_id
`
mssql
.raw(sql)
.catch(cb)
.then(function(info) {
// info.fieldName = table.fieldName
// info.dbTableName = table.dbTableName
cb(null, (info || []).map(camelCaseKeys))
})
},

close: function(cb) {
mssql.destroy(cb)
}
}
}

function camelCaseKeys(obj) {
return mapKeys(obj, function(val, key) {
return camelCase(key)
})
}
63 changes: 25 additions & 38 deletions backends/mysql.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
/* eslint camelcase: 0 */
'use strict';
'use strict'

var knex = require('knex');
var pluck = require('lodash/collection/pluck');
var mapKeys = require('lodash/object/mapKeys');
var contains = require('lodash/collection/includes');
var camelCase = require('lodash/string/camelCase');
var undef;
const knex = require('knex')
const pluck = require('lodash/map')
const mapKeys = require('lodash/mapKeys')
const contains = require('lodash/includes')
const camelCase = require('lodash/camelCase')
let undef

module.exports = function mysqlBackend(opts, callback) {
var mysql = knex({
const mysql = knex({
client: 'mysql',
connection: opts
});
})

process.nextTick(callback);
process.nextTick(callback)

return {
getTables: function(tableNames, cb) {
var matchAll = tableNames.length === 1 && tableNames[0] === '*';
const matchAll = tableNames.length === 1 && tableNames[0] === '*'

mysql
.select('table_name')
Expand All @@ -27,16 +27,16 @@ module.exports = function mysqlBackend(opts, callback) {
.where('table_type', 'BASE TABLE')
.catch(cb)
.then(function(tbls) {
tbls = pluck(tbls, 'table_name');
tbls = pluck(tbls, 'table_name')

if (!matchAll) {
tbls = tbls.filter(function(tbl) {
return contains(tableNames, tbl);
});
return contains(tableNames, tbl)
})
}

cb(null, tbls);
});
cb(null, tbls)
})
},

getTableComment: function(tableName, cb) {
Expand All @@ -49,8 +49,8 @@ module.exports = function mysqlBackend(opts, callback) {
})
.catch(cb)
.then(function(info) {
cb(null, info ? info.comment || undef : undef);
});
cb(null, info ? info.comment || undef : undef)
})
},

getTableStructure: function(tableName, cb) {
Expand All @@ -73,31 +73,18 @@ module.exports = function mysqlBackend(opts, callback) {
.orderBy('ordinal_position', 'asc')
.catch(cb)
.then(function(info) {
cb(null, (info || []).map(camelCaseKeys));
});
},

hasDuplicateValues: function(table, column, cb) {
mysql
.count(column + ' as hasSameValues')
.from(table)
.groupBy(column)
.having('hasSameValues', '>', 1)
.limit(1)
.catch(cb)
.then(function(info) {
cb(null, (info || []).length > 0);
});
cb(null, (info || []).map(camelCaseKeys))
})
},

close: function(cb) {
mysql.destroy(cb);
mysql.destroy(cb)
}
};
};
}
}

function camelCaseKeys(obj) {
return mapKeys(obj, function(val, key) {
return camelCase(key);
});
return camelCase(key)
})
}
Loading