New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

sqltyper

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sqltyper - npm Package Compare versions

Comparing version 0.2.0 to 0.2.1

12

CHANGELOG.md
# Changelog
===========
## 0.2.1
- **Enhancements**
- Infer the row count of a "calculator" SELECT statement as one
- Support boolean, null and float constants
- Support the CASE expression
- **Bug fixes**
- Treat qualified and unqualified column as equal if the column name
is the same
- Param is not replaced correctly if it's used more than once
## 0.2.0

@@ -5,0 +17,0 @@

@@ -51,2 +51,6 @@ "use strict";

Expression.createArraySubQuery = createArraySubQuery;
function createCase(branches, else_) {
return { kind: 'Case', branches, else_ };
}
Expression.createCase = createCase;
function createTypeCast(lhs, targetType) {

@@ -86,2 +90,4 @@ return { kind: 'TypeCast', lhs, targetType };

: handlers.arraySubQuery(expr);
case 'Case':
return handlers.case == null ? elseVal : handlers.case(expr);
case 'TypeCast':

@@ -116,2 +122,4 @@ return handlers.typeCast == null ? elseVal : handlers.typeCast(expr);

return handlers.arraySubQuery(expr);
case 'Case':
return handlers.case(expr);
case 'TypeCast':

@@ -125,2 +133,7 @@ return handlers.typeCast(expr);

case 'ColumnRef':
// `tbl.col` and `col` in an expression context must point to
// the same column. Otherwise the expression would be invalid
// because of an unambiguous column reference.
if (b.kind === 'TableColumnRef')
return a.column === b.column;
if (a.kind !== b.kind)

@@ -130,2 +143,7 @@ return false;

case 'TableColumnRef':
// `tbl.col` and `col` in an expression context must point to
// the same column. Otherwise the expression would be invalid
// because of an unambiguous column reference.
if (b.kind === 'ColumnRef')
return a.column === b.column;
if (a.kind !== b.kind)

@@ -176,2 +194,8 @@ return false;

return false; // TODO
case 'Case':
if (a.kind !== b.kind)
return false;
return (R.zip(a.branches, b.branches).every(([ab, bb]) => equals(ab.condition, bb.condition) && equals(ab.result, bb.result)) &&
((a.else_ !== null && b.else_ !== null && equals(a.else_, b.else_)) ||
(a.else_ === null && b.else_ === null)));
case 'TypeCast':

@@ -178,0 +202,0 @@ if (a.kind !== b.kind)

@@ -12,2 +12,3 @@ "use strict";

'BY',
'CASE',
'CROSS',

@@ -18,2 +19,4 @@ 'DEFAULT',

'DISTINCT',
'ELSE',
'END',
'EXCEPT',

@@ -56,2 +59,3 @@ 'EXISTS',

'SYMMETRIC',
'THEN',
'TRUE',

@@ -63,2 +67,3 @@ 'UNION',

'VALUES',
'WHEN',
'WHERE',

@@ -65,0 +70,0 @@ 'WITH',

29

dist/infer.js

@@ -270,7 +270,18 @@ "use strict";

})),
case: ({ branches, else_ }) => {
if (else_ === null) {
// No ELSE branch => Rows that match none of the branches
// will be NULL
return anyTE(true);
}
return pipeable_1.pipe(TaskEither.right((branchNullabilities) => (elseNullability) => branchNullabilities.reduce((a, b) => FieldNullability.disjunction(a)(b), elseNullability)), TaskEither.ap(pipeable_1.pipe(branches.map(({ condition, result }) => {
const nonNullExprsByCond = getNonNullSubExpressionsFromRowCond(condition);
return inferExpressionNullability(client, outsideCTEs, sourceColumns, [...nonNullExprsByCond, ...nonNullExprs], result);
}), fp_utils_1.sequenceATE)), TaskEither.ap(inferExpressionNullability(client, outsideCTEs, sourceColumns, nonNullExprs, else_)));
},
// A type cast evaluates to NULL if the expression to be casted is
// NULL.
typeCast: ({ lhs }) => inferExpressionNullability(client, outsideCTEs, sourceColumns, nonNullExprs, lhs),
// A constant is never NULL
constant: () => anyTE(false),
// NULL is the only nullable constant
constant: ({ valueText }) => anyTE(valueText === 'NULL'),
// A parameter can be NULL

@@ -407,5 +418,13 @@ parameter: () => anyTE(true),

const rowCount = ast.walk(astNode, {
select: ({ limit }) => limit && limit.count && isConstantExprOf('1', limit.count)
? 'zeroOrOne' // LIMIT 1 => zero or one rows
: 'many',
select: ({ body, setOps, limit }) => {
if (setOps.length === 0 && body.from === null) {
// No UNION/INTERSECT/EXCEPT, no FROM clause => one row
return 'one';
}
if (limit && limit.count && isConstantExprOf('1', limit.count)) {
// LIMIT 1 => zero or one row
return 'zeroOrOne';
}
return 'many';
},
insert: ({ values, returning }) => ast.Values.walk(values, {

@@ -412,0 +431,0 @@ // INSERT INTO xxx DEFAULT VALUES always creates a single row

7

dist/parser/index.js

@@ -22,2 +22,5 @@ "use strict";

const arraySubQueryExpr = typed_parser_1.seq((_arr, subquery) => ast_1.Expression.createArraySubQuery(subquery), token_1.reservedWord('ARRAY'), parenthesized(typed_parser_1.lazy(() => select)));
const caseBranch = typed_parser_1.seq((_when, condition, _then, result) => ({ condition, result }), token_1.reservedWord('WHEN'), typed_parser_1.lazy(() => expression), token_1.reservedWord('THEN'), typed_parser_1.lazy(() => expression));
const caseElse = typed_parser_1.seq(typed_parser_1.$2, token_1.reservedWord('ELSE'), typed_parser_1.lazy(() => expression));
const caseExpr = typed_parser_1.seq((_case, branch1, branches, else_, _end) => ast_1.Expression.createCase([branch1, ...branches], else_), token_1.reservedWord('CASE'), caseBranch, typed_parser_1.many(caseBranch), utils_1.optional(caseElse), token_1.reservedWord('END'));
const functionArguments = parenthesized(typed_parser_1.oneOf(

@@ -34,7 +37,7 @@ // func(*} means no arguments

const stringConstant = typed_parser_1.seq(typed_parser_1.$2, token_1.symbolKeepWS("'"), strInner, token_1.symbol("'"));
const constantExpr = typed_parser_1.seq((val, _ws) => ast_1.Expression.createConstant(val), typed_parser_1.oneOf(typed_parser_1.match('[0-9]+'), stringConstant), token_1._);
const constantExpr = typed_parser_1.seq((val, _ws) => ast_1.Expression.createConstant(val), typed_parser_1.oneOf(token_1.expectIdentifier('TRUE'), token_1.expectIdentifier('FALSE'), token_1.expectIdentifier('NULL'), typed_parser_1.match('[0-9]+(\\.[0-9]+)?([eE]-?[0-9]+)?'), typed_parser_1.match('\\.[0-9]+'), stringConstant), token_1._);
const parameterExpr = typed_parser_1.seq((_$, index, _ws) => ast_1.Expression.createParameter(index), token_1.symbolKeepWS('$'), typed_parser_1.int('[0-9]+'), token_1._);
const parenthesizedExpr = parenthesized(typed_parser_1.lazy(() => expression));
const typeName = typed_parser_1.seq((id, arraySuffix) => id + arraySuffix, token_1.identifier, typed_parser_1.oneOf(typed_parser_1.seq(_ => '[]', token_1.symbol('['), token_1.symbol(']')), typed_parser_1.seq(_ => '', token_1._)));
const primaryExpr = typed_parser_1.seq((expr, typeCast) => typeCast != null ? ast_1.Expression.createTypeCast(expr, typeCast) : expr, typed_parser_1.oneOf(arraySubQueryExpr, typed_parser_1.attempt(special_functions_1.specialFunctionCall(typed_parser_1.lazy(() => primaryExpr))), columnRefOrFunctionCallExpr, constantExpr, parameterExpr, parenthesizedExpr), utils_1.optional(typed_parser_1.seq(typed_parser_1.$2, token_1.symbol('::'), typeName)));
const primaryExpr = typed_parser_1.seq((expr, typeCast) => typeCast != null ? ast_1.Expression.createTypeCast(expr, typeCast) : expr, typed_parser_1.oneOf(arraySubQueryExpr, caseExpr, typed_parser_1.attempt(special_functions_1.specialFunctionCall(typed_parser_1.lazy(() => primaryExpr))), columnRefOrFunctionCallExpr, constantExpr, parameterExpr, parenthesizedExpr), utils_1.optional(typed_parser_1.seq(typed_parser_1.$2, token_1.symbol('::'), typeName)));
function makeUnaryOp(oper, nextExpr) {

@@ -41,0 +44,0 @@ return typed_parser_1.seq((ops, next) => ops.length > 0

@@ -25,3 +25,3 @@ "use strict";

function expectIdentifier(ident) {
return typed_parser_1.seq(typed_parser_1.$1, typed_parser_1.attempt(typed_parser_1.map((match, toError) => match.toLowerCase() !== ident.toLowerCase()
return typed_parser_1.seq(_ => ident, typed_parser_1.attempt(typed_parser_1.map((match, toError) => match.toLowerCase() !== ident.toLowerCase()
? toError(`Expected ${ident}, got ${match}`)

@@ -28,0 +28,0 @@ : ident, exports.matchIdentifier)), exports._);

@@ -42,3 +42,3 @@ "use strict";

for (const [name, index] of paramIndices) {
mangledSQL = mangledSQL.replace('${' + name + '}', '$' + index);
mangledSQL = mangledSQL.replace(new RegExp('\\$\\{' + name + '\\}', 'g'), '$' + index);
}

@@ -45,0 +45,0 @@ // Iterating a Map is guaranteed to yield in the insertion order

{
"name": "sqltyper",
"version": "0.2.0",
"version": "0.2.1",
"description": "Typed SQL queries in PostgreSQL",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -5,6 +5,11 @@ # sqltyper - Type your SQL queries!

SQL is a typed language. sqltyper takes raw PostgreSQL queries and
generates TypeScript functions that run those queries AND are typed
correctly, based on the database schema.
SQL is a typed language, but most solutions for using an SQL database
from typed languages don't make use of that typing information in a
way that would actually help you catch bugs during development.
**sqltyper** takes raw PostgreSQL queries and generates TypeScript
functions that run those queries AND are typed correctly, based on the
database schema. This makes it possible for the TypeScript compiler to
find bugs in your code that interacts with an SQL database.
For example, given the following schema:

@@ -15,3 +20,3 @@

name text NOT NULL,
age integer NOT NULL,
age integer,
shoe_size integer

@@ -24,3 +29,6 @@ )

```sql
SELECT initcap(name) as name_capitalized, age, shoe_size
SELECT
initcap(name) as name_capitalized,
age,
shoe_size
FROM person

@@ -131,6 +139,6 @@ WHERE

These functions a `Client` or `Pool` from [node-postgres] as the first
argument and possible query parameters as the second parameter.
These functions take a `Client` or `Pool` from [node-postgres] as the
first argument, and possible query parameters as the second parameter.
It will return one of the following, wrapped in a `Promise`:
They will return one of the following, wrapped in a `Promise`:

@@ -142,6 +150,6 @@ - An array of result objects, with object keys corresponding to output

- A single result row or `null` if the query only ever returns zero or
one row (e.g. `SELECT` query with `LIMIT 1`).
- A single result object or `null` if the query only ever returns zero
or one row (e.g. `SELECT` query with `LIMIT 1`).
- A number which denotes the number of affected rows (`INSERT`,
- A number which denotes the number of affected rows (e.g. `INSERT`,
`UPDATE` or `DELETE` without a `RETURNING` clause).

@@ -172,4 +180,4 @@

Database URI to connect to, e.g. `-d postgres://user:pass@localhost:5432/mydb`.
By default, uses the [connecting logic] of node-postgres that
relies on environment variables.
If not given, uses the [connecting logic] of node-postgres that relies
on [libpq environment variables].

@@ -194,7 +202,7 @@ `--ext`, `-e`

updating them. If they are, exit with status 0, otherwise exit with
status 1. Useful for CI or pre-commit hooks.
status 1. Useful for CI or pre-commit hooks. Default: `false`.
`--prettify`, `-p`
Apply `prettier` to output TypeScript files. `prettier` must be
Apply [prettier] to generated TypeScript files. [prettier] must be
installed and configured for your project. Default: `false`.

@@ -212,2 +220,4 @@

[connecting logic]: https://node-postgres.com/features/connecting
[libpq environment variables]: https://www.postgresql.org/docs/current/libpq-envars.html
[prettier]: https://prettier.io/

@@ -252,7 +262,7 @@

The main motivator for sqltyper was [sqlτyped] by @jonifreeman. It
does more or less the same as sqltyper, but for Scala, and meant to be
used with MySQL. It uses JDBC, and is implemented as a Scala macro
rather than an offline code generation tool.
The main motivator for sqltyper was [sqlτyped] by Joni Freeman. It
does more or less the same as sqltyper, but for Scala, and is designed
to be used with MySQL. It uses JDBC, and is implemented as a Scala
macro rather than an offline code generation tool.
[sqlτyped]: https://github.com/jonifreeman/sqltyped
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc