Skip to content

Commit

Permalink
Type converters (#49)
Browse files Browse the repository at this point in the history
* implement tpye converters

* add gitpod init

* add gitpod init

* add docker compose

* update type converters

* 2.5.9
  • Loading branch information
kbarbounakis authored Sep 24, 2024
1 parent 516db26 commit 7a452c3
Show file tree
Hide file tree
Showing 13 changed files with 3,567 additions and 2,902 deletions.
6 changes: 4 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"parser": "babel-eslint",
"extends": [
"eslint:recommended",
"plugin:node/recommended"
],
"env": {
"browser": true,
"node": true,
Expand All @@ -18,7 +21,6 @@
"spyOn": false,
"jasmine":false
},
"extends": "eslint:recommended",
"rules": {
"no-console": "off",
"no-invalid-this": "warn",
Expand Down
20 changes: 0 additions & 20 deletions .github/workflows/npm-publish-next.yml

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/npmpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
- run: npm publish --tag lts
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
20 changes: 20 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: |
docker-compose pull
npm ci
echo -e "DB_HOST=localhost" >> .env
echo -e "DB_PORT=1433" >> .env
echo -e "DB_USER=sa" >> .env
echo -e "DB_PASSWORD=Str0ngPa\$w0rd" >> .env
- command: |
docker-compose up
ports:
- port: 1433
onOpen: ignore

vscode:
extensions:
- ms-azuretools.vscode-docker
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ node_modules
.vscode
.idea
.DS_Store
# gitpod
.gitpod.yml
docker-compose.yml
50 changes: 24 additions & 26 deletions MSSqlAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const mssql = require('mssql');
const async = require('async');
const util = require('util');
const { TraceUtils } = require('@themost/common');
const { QueryExpression, SqlUtils } = require('@themost/query');
const { SqlUtils } = require('@themost/query');
const { MSSqlFormatter } = require('./MSSqlFormatter');
const { TransactionIsolationLevelFormatter } = require('./TransactionIsolationLevel')
/**
Expand Down Expand Up @@ -270,15 +270,15 @@ class MSSqlAdapter {
const db = inTransaction ? new MSSqlAdapter(this.options) : this;
// create migration schema
const migration = {
"appliesTo": "increment_id",
"model": "increments",
"version": "1.0",
"description": "Increments migration (version 1.0)",
"add": [
{ "name": "id", "type": "Counter", "primary": true },
{ "name": "entity", "type": "Text", "size": 120 },
{ "name": "attribute", "type": "Text", "size": 120 },
{ "name": "value", "type": "Integer" }
'appliesTo': 'increment_id',
'model': 'increments',
'version': '1.0',
'description': 'Increments migration (version 1.0)',
'add': [
{ 'name': 'id', 'type': 'Counter', 'primary': true },
{ 'name': 'entity', 'type': 'Text', 'size': 120 },
{ 'name': 'attribute', 'type': 'Text', 'size': 120 },
{ 'name': 'value', 'type': 'Integer' }
]
};
//ensure increments entity
Expand Down Expand Up @@ -495,6 +495,9 @@ class MSSqlAdapter {
case 'Short':
s = 'smallint';
break;
case 'Json':
s = 'nvarchar(max)';
break;
default:
s = 'int';
break;
Expand Down Expand Up @@ -594,12 +597,12 @@ class MSSqlAdapter {
*/
columns: function (callback) {
callback = callback || function () { };
self.execute("SELECT c0.[name] AS [name], c0.[isnullable] AS [nullable], c0.[length] AS [size], c0.[prec] AS [precision], " +
"c0.[scale] AS [scale], t0.[name] AS type, t0.[name] + CASE WHEN t0.[variable]=0 THEN '' ELSE '(' + CONVERT(varchar,c0.[length]) + ')' END AS [type1], " +
"CASE WHEN p0.[indid]>0 THEN 1 ELSE 0 END [primary] FROM syscolumns c0 INNER JOIN systypes t0 ON c0.[xusertype] = t0.[xusertype] " +
"INNER JOIN sysobjects s0 ON c0.[id]=s0.[id] LEFT JOIN (SELECT k0.* FROM sysindexkeys k0 INNER JOIN (SELECT i0.* FROM sysindexes i0 " +
"INNER JOIN sysobjects s0 ON i0.[id]=s0.[id] WHERE i0.[status]=2066) x0 ON k0.[id]=x0.[id] AND k0.[indid]=x0.[indid] ) p0 ON c0.[id]=p0.[id] " +
"AND c0.[colid]=p0.[colid] WHERE s0.[name]=? AND s0.[xtype]='U' AND SCHEMA_NAME(s0.[uid])=?", [table, owner], function (err, result) {
self.execute('SELECT c0.[name] AS [name], c0.[isnullable] AS [nullable], c0.[length] AS [size], c0.[prec] AS [precision], ' +
'c0.[scale] AS [scale], t0.[name] AS type, t0.[name] + CASE WHEN t0.[variable]=0 THEN \'\' ELSE \'(\' + CONVERT(varchar,c0.[length]) + \')\' END AS [type1], ' +
'CASE WHEN p0.[indid]>0 THEN 1 ELSE 0 END [primary] FROM syscolumns c0 INNER JOIN systypes t0 ON c0.[xusertype] = t0.[xusertype] ' +
'INNER JOIN sysobjects s0 ON c0.[id]=s0.[id] LEFT JOIN (SELECT k0.* FROM sysindexkeys k0 INNER JOIN (SELECT i0.* FROM sysindexes i0 ' +
'INNER JOIN sysobjects s0 ON i0.[id]=s0.[id] WHERE i0.[status]=2066) x0 ON k0.[id]=x0.[id] AND k0.[indid]=x0.[indid] ) p0 ON c0.[id]=p0.[id] ' +
'AND c0.[colid]=p0.[colid] WHERE s0.[name]=? AND s0.[xtype]=\'U\' AND SCHEMA_NAME(s0.[uid])=?', [table, owner], function (err, result) {
if (err) {
return callback(err);
}
Expand Down Expand Up @@ -834,7 +837,7 @@ class MSSqlAdapter {
}
try {
const formatter = new MSSqlFormatter();
const sql = "EXECUTE('" + util.format('CREATE VIEW %s.%s AS ', formatter.escapeName(owner), formatter.escapeName(view)) + formatter.format(q) + "')";
const sql = 'EXECUTE(\'' + util.format('CREATE VIEW %s.%s AS ', formatter.escapeName(owner), formatter.escapeName(view)) + formatter.format(q) + '\')';
self.execute(sql, [], tr);
}
catch (e) {
Expand Down Expand Up @@ -868,7 +871,7 @@ class MSSqlAdapter {
const self = this;
const migration = obj;
if (migration.appliesTo == null)
throw new Error("Invalid argument. Model name is undefined.");
throw new Error('Invalid argument. Model name is undefined.');
self.open(function (err) {
if (err) {
callback.bind(self)(err);
Expand Down Expand Up @@ -1063,6 +1066,7 @@ class MSSqlAdapter {
*/
database(name) {
const self = this;
const formatter = new MSSqlFormatter();
let db = name;
let owner = 'dbo';
const matches = /(\w+)\.(\w+)/.exec(name);
Expand All @@ -1072,10 +1076,7 @@ class MSSqlAdapter {
}
return {
exists: function (callback) {
const query = new QueryExpression().from('sys.databases').where('name').equal(db)
.and('SCHEMA_NAME(owner_sid)').equal(owner)
.select('name');
self.execute(query, null, (err, res) => {
self.execute(`SELECT [name] FROM sys.databases WHERE [name]=${formatter.escape(db)} AND SCHEMA_NAME(owner_sid)=${formatter.escape(owner)}`, null, (err, res) => {
if (err) {
return callback(err);
}
Expand All @@ -1093,10 +1094,7 @@ class MSSqlAdapter {
});
},
create: function (callback) {
const query = new QueryExpression().from('sys.databases').where('name').equal(db)
.and('SCHEMA_NAME(owner_sid)').equal(owner)
.select('name');
self.execute(query, null, (err, res) => {
self.execute(`SELECT [name] FROM sys.databases WHERE [name]=${formatter.escape(db)} AND SCHEMA_NAME(owner_sid)=${formatter.escape(owner)}`, null, (err, res) => {
if (err) {
return callback(err);
}
Expand Down
146 changes: 121 additions & 25 deletions MSSqlFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://themost.io/license
*/
const util = require('util');
const { QueryField, QueryUtils, SqlUtils, SqlFormatter } = require('@themost/query');
const { QueryField, QueryExpression, SqlFormatter, ObjectNameValidator } = require('@themost/query');

function zeroPad(number, length) {
number = number || 0;
Expand Down Expand Up @@ -114,32 +114,20 @@ class MSSqlFormatter extends SqlFormatter {
* @param {boolean=} unquoted
*/
escape(value, unquoted) {
if (value === null || typeof value === 'undefined')
return SqlUtils.escape(null);
if (typeof value === 'string')
return 'N\'' + value.replace(/'/g, "''") + '\'';
if (typeof value === 'boolean')
if (typeof ObjectNameValidator === 'undefined') {
throw new Error('ObjectNameValidator is required for using the current version of MSSQL adapter. Consider updating project dependencies.');
}
if (typeof value === 'boolean') {
return value ? '1' : '0';
if (typeof value === 'object') {
//add an exception for Date object
if (value instanceof Date)
return this.escapeDate(value);
if (value.hasOwnProperty('$name'))
return this.escapeName(value.$name);
}
if (unquoted)
return value.valueOf();
else
return SqlUtils.escape(value);
}
escapeName(name) {
if (typeof name === 'string') {
if (/^(\w+)\.(\w+)$/g.test(name)) {
return name.replace(/(\w+)/g, "[$1]");
}
return name.replace(/(\w+)$|^(\w+)$/g, "[$1]");
if (value instanceof Date) {
return this.escapeDate(value);
}
if (typeof value === 'string') {
const str = value.replace(/'/g, '\'\'');
return unquoted ? str : ('N\'' + str + '\'');
}
return name;
return super.escape.bind(this)(value, unquoted);
}
/**
* @param {Date|*} val
Expand All @@ -155,7 +143,7 @@ class MSSqlFormatter extends SqlFormatter {
const millisecond = zeroPad(val.getMilliseconds(), 3);
//format timezone
const offset = val.getTimezoneOffset(), timezone = (offset <= 0 ? '+' : '-') + zeroPad(-Math.floor(offset / 60), 2) + ':' + zeroPad(offset % 60, 2);
return "CONVERT(datetimeoffset,'" + year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second + "." + millisecond + timezone + "')";
return 'CONVERT(datetimeoffset,\'' + year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second + '.' + millisecond + timezone + '\')';
}
/**
* Implements startsWith(a,b) expression formatter.
Expand Down Expand Up @@ -204,6 +192,114 @@ class MSSqlFormatter extends SqlFormatter {
$trim(p0) {
return util.format('LTRIM(RTRIM((%s)))', this.escape(p0));
}

$ifnull(p0, p1) {
return util.format('ISNULL(%s, %s)', this.escape(p0), this.escape(p1));
}

$ifNull(p0, p1) {
return util.format('ISNULL(%s, %s)', this.escape(p0), this.escape(p1));
}

isLogical(obj) {
for (const key in obj) {
return (/^\$(and|or|not|nor)$/g.test(key));
}
return false;
}

$cond(ifExpr, thenExpr, elseExpr) {
// validate ifExpr which should an instance of QueryExpression or a comparison expression
let ifExpression;
if (ifExpr instanceof QueryExpression) {
ifExpression = this.formatWhere(ifExpr.$where);
} else if (this.isComparison(ifExpr) || this.isLogical(ifExpr)) {
ifExpression = this.formatWhere(ifExpr);
} else {
throw new Error('Condition parameter should be an instance of query or comparison expression');
}
return util.format('(CASE WHEN %s THEN %s ELSE %s END)', ifExpression, this.escape(thenExpr), this.escape(elseExpr));
}

/**
* @param {*} expr
* @return {string}
*/
$jsonGet(expr) {
if (typeof expr.$name !== 'string') {
throw new Error('Invalid json expression. Expected a string');
}
const parts = expr.$name.split('.');
const extract = this.escapeName(parts.splice(0, 2).join('.'));
return `JSON_VALUE(${extract}, '$.${parts.join('.')}')`;
}

/**
* @param {*} expr
* @return {string}
*/
$jsonArray(expr) {
if (typeof expr.$name !== 'string') {
throw new Error('Invalid json expression. Expected a string');
}
const parts = expr.$name.split('.');
const extract = this.escapeName(parts.splice(0, 2).join('.'));
return `JSON_QUERY(${extract}, '$.${parts.join('.')}')`;
}

$uuid() {
return 'NEWID()'
}

$toString(p0) {
return util.format('CAST(%s AS NVARCHAR)', this.escape(p0));
}

$toGuid(expr) {
return util.format('dbo.BIN_TO_UUID(HASHBYTES(\'MD5\',CONVERT(VARCHAR(MAX), %s)))', this.escape(expr));
}

$toInt(expr) {
return util.format('FLOOR(CAST(%s AS DECIMAL(19,8)))', this.escape(expr));
}

$toDouble(expr) {
return this.$toDecimal(expr, 19, 8);
}

/**
* @param {*} expr
* @param {number=} precision
* @param {number=} scale
* @returns
*/
$toDecimal(expr, precision, scale) {
const p = typeof precision === 'number' ? parseInt(precision,10) : 19;
const s = typeof scale === 'number' ? parseInt(scale,10) : 8;
return util.format('CAST(%s as DECIMAL(%s,%s))', this.escape(expr), p, s);
}

$toLong(expr) {
return util.format('CAST(%s AS BIGINT)', this.escape(expr));
}

/**
*
* @param {('date'|'datetime'|'timestamp')} type
* @returns
*/
$getDate(type) {
switch (type) {
case 'date':
return 'CAST(GETDATE() AS DATE)';
case 'datetime':
return 'CAST(GETDATE() AS DATETIME)';
case 'timestamp':
return 'CAST(GETDATE() AS DATETIMEOFFSET)';
default:
return 'GETDATE()'
}
}
}

module.exports = {
Expand Down
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.8'

services:
mssql:
image: mcr.microsoft.com/mssql/server
container_name: mssql
environment:
ACCEPT_EULA: Y
MSSQL_SA_PASSWORD: Str0ngPa$$w0rd
ports:
- "1433:1433"
networks:
- mssql
networks:
mssql:
driver: bridge
Loading

0 comments on commit 7a452c3

Please sign in to comment.