Coder Social home page Coder Social logo

hediet / ts-typed-sql Goto Github PK

View Code? Open in Web Editor NEW
54.0 5.0 4.0 3.13 MB

A fully typed sql builder. Not maintained anymore, use it for your inspiration.

Home Page: https://hediet.github.io/ts-typed-sql/

TypeScript 6.31% JavaScript 93.67% SCSS 0.02%

ts-typed-sql's Introduction

@hediet/typed-sql

Build Status Coverage Status

A fully typed sql builder. Works best with TypeScript an Visual Studio Code. Currently only has support for PostgreSql, however, it should be very easy to implement SQL Generators for other SQL dialects.

Installation

@hediet/typed-sql can be installed via the node package manager using the command npm install @hediet/typed-sql --save.

Usage

This documentation is far from complete. However, most of the features are self-explanatory and can easily be explored by using the intellisense. Intellisense works great when using this library from TypeScript in VS Code.

Preparation

For proper typing, all used tables must be defined:

import { table, column, tText, tInteger } from "@hediet/typed-sql";

const contacts = table({ name: "contacts", schema: "public" },
	{
		firstname: tText,
		lastname: tText,
		mother_id: tInteger.orNull(),
		father_id: tInteger.orNull(),
	},
	{ id: tInteger }
);

...

Connection

SQL queries are processed by an instance of DbConnection. To construct a DbConnection, a query service is required:

import { DbConnection, PostgreQueryService } from "@hediet/typed-sql";
import pg = require("pg");

const pool = new pg.Pool({
	database: "postgres",
	user: "postgres",
	password: "FXLjrQ0"
});

const queryService = new PostgreQueryService(pool, { shortenColumnNameIfUnambigous: true, skipQuotingIfNotRequired: true });
const dbCon = new DbConnection(queryService);

Some Queries

Queries are independent of DbConnection, however, an instance of DbConnection is needed to execute queries. If q is a query, it can be executed by one of the methods that DbConnection provides:

await dbCon.exec(q); // returns all rows
await dbCon.firstOrUndefined(q); // returns the first row if there is any, otherwise undefined.
await dbCon.first(q); // returns the first row and throws an error if there is no row.
await dbCon.single(q); // returns the first row and ensures there is only one.

Select Queries

Basic Select
import { select, concat } from "hediet-typed-sql";

// Selects the id column from the contacts table.
await dbCon.exec(from(contacts).select("id"));

from(contacts).select(contacts.id.as("myId")); // selects id and renames it to "myId"
from(contacts).select(contacts.$all); // selects all columns

// Even complex expressions can be selected
from(contacts).select(concat(contacts.firstname, " ", contacts.lastname).as("fullName"));
Where
// a where clause takes any expression of type boolean.
from(contacts).where(contacts.name.isLike("Jon%");
Joins
const p = contacts.as("parents");

from(contacts)
	.leftJoin(p).on(
		p.id.isEqualTo(contacts.mother_id).or(p.id.isEqualTo(contacts.father_id))
	)

Delete

deleteFrom(contacts)
	.where(contacts.id.isIn([1, 2, 3]))
	.returning("id")

Insert

const id = await dbCon.firstValue(
	insertInto(contacts)
		.value({ firstname: "Hoster", lastname: "Tully", father_id: null, mother_id: null })
		.returning("id")
);

insertInto(contacts).valuesFrom(
	from(contacts)
		.select("firstname", "father_id", "mother_id")
		.select(concat(contacts.lastname, "2").as("lastname"))
)

Update

update(contacts)
	.set({ firstname: "test" })
	.where({ id: 1 })

ts-typed-sql's People

Contributors

hediet avatar phiresky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

phiresky apalm

ts-typed-sql's Issues

Allow tuples

e.g.

select mensa.meals where (_mensaid, _lineid) in ((a,b), (c,d))

Put types in their own namespace

I think it would be better to name the types more exactly like their pg counterparts
e.g.

import {types as t} from '@hediet/typed-sql';
t.smallint
t.integer
t.bigint
t.decimal
t.numeric
t.real
t.double_precision

Timestamptz type

class TimestampTZType extends s.Type<Date, Date, "date"> {
    name = 'timestamptz';
    serialize(arg: Date): string | number | boolean {
        return arg as any;
    }

    deserialize(arg: string | number | boolean): Date {
        return new Date("" + arg);
    }
}
export const timestamptz = new TimestampTZType;

typescript >= 2.6 compatibility

partial fix:

commit d10ea00a2e674da8d680559512940b7ecda9a77a
Author: phiresky <[email protected]>
Date:   Wed Nov 8 16:38:16 2017 +0100

    fix type error on typescript >= 2.6

diff --git a/src/AST/Expressions.ts b/src/AST/Expressions.ts
index fd4c3c7..6e1030b 100644
--- a/src/AST/Expressions.ts
+++ b/src/AST/Expressions.ts
@@ -291,33 +291,34 @@ export interface ConcreteBinaryExpressionStatic<TLeft extends AnyType, TRight ex
 	new (...args: any[]): ConcreteBinaryExpression<TLeft, TRight, TResult>;
 }
 
-export function ConcreteBinaryExpression<TLeft extends AnyType, TRight extends AnyType, TResult extends AnyType>(
-		symbol: string, resultType: TResult, precedenceLevel: number): ConcreteBinaryExpressionStatic<TLeft, TRight, TResult> {
-	return class extends BinaryOperatorExpression<TLeft, TRight, TResult> {
+export function ConcreteBinaryExpression<TResult extends AnyType>(
+		symbol: string, resultType: TResult, precedenceLevel: number) {
+	return class ConcreteBinaryExpression<TLeft extends AnyType, TRight extends AnyType> extends BinaryOperatorExpression<TLeft, TRight, TResult> {
 		constructor(left: Expression<TLeft>, right: Expression<TRight>) { super(left, right, resultType); }
 		public get operator(): string { return symbol; }
 		public get precedenceLevel(): number { return precedenceLevel; }
 	};
 }
 
-export class AdditionExpression extends ConcreteBinaryExpression<IntegerType, IntegerType, IntegerType>("+", tInteger, 3) {}
-export class SubtractionExpression extends ConcreteBinaryExpression<IntegerType, IntegerType, IntegerType>("-", tInteger, 3) {}
-export class MultiplicationExpression extends ConcreteBinaryExpression<IntegerType, IntegerType, IntegerType>("*", tInteger, 2) {}
-export class DivisionExpression extends ConcreteBinaryExpression<IntegerType, IntegerType, IntegerType>("/", tInteger, 2) {}
-export class ModulusExpression extends ConcreteBinaryExpression<IntegerType, IntegerType, IntegerType>("%", tInteger, 2) {}
+export class AdditionExpression extends ConcreteBinaryExpression<IntegerType>("+", tInteger, 3)<IntegerType, IntegerType> {}
+export class SubtractionExpression extends ConcreteBinaryExpression<IntegerType>("-", tInteger, 3)<IntegerType, IntegerType> {}
+export class MultiplicationExpression extends ConcreteBinaryExpression<IntegerType>("*", tInteger, 2)<IntegerType, IntegerType> {}
+export class DivisionExpression extends ConcreteBinaryExpression<IntegerType>("/", tInteger, 2)<IntegerType, IntegerType> {}
+export class ModulusExpression extends ConcreteBinaryExpression<IntegerType>("%", tInteger, 2)<IntegerType, IntegerType> {}
 
 
-export class EqualsExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("=", tBoolean, 4) {}
-export class UnequalsExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("!=", tBoolean, 4) {}
-export class GreaterExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>(">", tBoolean, 4) {}
-export class LessExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("<", tBoolean, 4) {}
-export class GreaterOrEqualExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>(">=", tBoolean, 4) {}
-export class LessOrEqualExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("<=", tBoolean, 4) {}
-export class NotLessExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("!<", tBoolean, 4) {}
-export class NotGreaterExpression<T extends AnyType> extends ConcreteBinaryExpression<T, T, BooleanType>("!>", tBoolean, 4) {}
+export class EqualsExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("=", tBoolean, 4)<T, T> {}
 
-export class OrExpression extends ConcreteBinaryExpression<BooleanType, BooleanType, BooleanType>("OR", tBoolean, 7) {}
-export class AndExpression extends ConcreteBinaryExpression<BooleanType, BooleanType, BooleanType>("AND", tBoolean, 6) {}
+export class UnequalsExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("!=", tBoolean, 4)<T, T> {}
+export class GreaterExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>(">", tBoolean, 4)<T, T> {}
+export class LessExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("<", tBoolean, 4)<T, T> {}
+export class GreaterOrEqualExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>(">=", tBoolean, 4)<T, T> {}
+export class LessOrEqualExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("<=", tBoolean, 4)<T, T> {}
+export class NotLessExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("!<", tBoolean, 4)<T, T> {}
+export class NotGreaterExpression<T extends AnyType> extends ConcreteBinaryExpression<BooleanType>("!>", tBoolean, 4)<T, T> {}
+
+export class OrExpression extends ConcreteBinaryExpression< BooleanType>("OR", tBoolean, 7)<BooleanType, BooleanType> {}
+export class AndExpression extends ConcreteBinaryExpression< BooleanType>("AND", tBoolean, 6)<BooleanType, BooleanType> {}
 
 export class LikeExpression extends Expression<BooleanType> {
 	public get precedenceLevel() { return 7; }

FromItemToOutRow

export type FromItemToInRow<TFromItem extends s.Table<any, any>> =
    {[TName in keyof TFromItem["$requiredColumns"]]: s.GetInType<s.ExpressionTypeOf<TFromItem["$requiredColumns"][TName]>> };

debug log sql statements using require('debug')

I think something like this should be the default

const debugSql = require('debug')('typed-sql');
const qs: QueryService;

qs.onSqlStatement.sub((self, args) => {
    if (debugSql.enabled)
        debugSql(args.sql.substr(0, 400), args.sql.length > 400 ? "..." : "", args.parameters.slice(0, 20), args.parameters.length > 20 ? "..." : "");
});

Then you can debug the SQL statements generated by simly running the process using

DEBUG=typed-sql node script.js

tDate is completely broken

it serializes new Date() using .toString() which results in invalid input syntax for type timestamp with time zone: "Thu Dec 11 2014 00:00:00 GMT+0100 (CET)"

Typed Errors

Currently errors are thrown as normal errors: throw new Error("Expected at least one row.");

It would be good if they had some typed metadata that could be used as follows:

import {from, isTypedError} from "@hediet/typed-sql";

try {
    await db.exec(from(....));
} catch(e) {
    if (!isTypedError(e)) throw e;
    // e is fully typed here, and has meta data such as the sql error code / constraint name or the cause why typed-sql threw it.
}

implementation suggestion

typedErrorSymbol = Symbol("typedError");
export class TypedError extends Error {
    [typedErrorSymbol]: true;
    constructor(...) {}
}

export function isTypedError(e: any): e is TypedError {
    return e && e[typedErrorSymbol];
}

cast precedence error

a.plus(b).cast(tInteger)
produces

a+b::integer

but it should produce

(a+b)::integer

Cannot select expression

sql.select(sql.val(123))

The 'this' context of type 'typeof import(".../typed-sql")' is not assignable to method's 'this' of type 'SelectQuery<{}, {}, NoColumnsSelected>'.
  Type 'typeof import(".../typed-sql")' is missing the following properties from type 'SelectQuery<{}, {}, NoColumnsSelected>': _orderBys, _havingCondition, _groupBys, getState, and 23 more.ts(2684)

values params not correctly escaped (uppercase)

const existingMeals = await db.exec(
            sql.from(chkTable)
                .innerJoin(menu.meal)
                .on({ _mensa: chkTable._mensa,
                    _line: chkTable._line,
                    _date: chkTable._date.cast(timestamptz),
                    _mealIndex: chkTable._mealIndex })
                .select(menu.meal.$all)
        );
SELECT "menu"."meal".* FROM (VALUES ($1, $2, $3, $4), ($5, $6, $7, $8)) AS "temp"(_mensa, _date, _line, _mealIndex) JOIN "menu"."meal" ON "menu"."meal"."_mensa" = "temp"."_mensa" AND "menu"."meal"."_line" = "temp"."_line" AND "menu"."meal"."_date" = "temp"."_date"::timestamptz AND "menu"."meal"."_mealIndex" = "temp"."_mealIndex"

error: Perhaps you meant to reference the column "temp._mealindex".

Project status?

This project is pre-1.0 and was last updated 2018-02-12.

  1. Is the API stable and the implementation production-ready?
  2. What future fixes/additions -- if any -- are planned?

Output some warning when doing `x = x`

With .innerJoin or .leftJoin it's very easy to accidentaly get the parameters the wrong way around and there is no way to notice. For example

sql.from(chkTable)
    .innerJoin(menu.meal)
    .on({ _mensa: menu.meal._mensa, _line: menu.meal._line, _date: menu.meal._date, _mealIndex: menu.meal._mealIndex })
    .select(menu.meal.$all)

generates

SELECT "menu"."meal".* FROM (VALUES ($1, $2, $3, $4), ($5, $6, $7, $8))
AS "temp"(_mensa, _date, _line, _mealIndex)
JOIN "menu"."meal"
ON "menu"."meal"."_mensa" = "menu"."meal"."_mensa"
AND "menu"."meal"."_line" = "menu"."meal"."_line"
AND "menu"."meal"."_date" = "menu"."meal"."_date"
AND "menu"."meal"."_mealIndex" = "menu"."meal"."_mealIndex"

Which obviously gives unintended results

Can't match type unknown to int in update from values query

This is caused by postgresql not knowing the type of values() with parameterized values.

example:

const updateTmpTable = values(fromItemTypes(asset_classes, ["market_hash_name",  "can_deposit", "coin_price"]), updateValues).as("temp");

const updateQuery = update(asset_classes).from(updateTmpTable)
    .where({ market_hash_name: asset_classes.market_hash_name })
    .set({
        coin_price: updateTmpTable.coin_price.cast(tInteger),
        can_deposit: updateTmpTable.can_deposit.cast(tBoolean),
        updated: new Date()
    });

The casts in .set({ should not be needed but they are.

The problem is that for inline tables like

(values (100190, true), (100125, true)) as foo(id, exists)

you can not specify the types of the columns as far as I know.

The same is needed for the where query, otherwise I'm getting operator does not exist: text = bigint for a simple bigint primary key comparison because it apparently interprets the other value as text.

Allow getting result meta information

With pg you can get the number of updated / inserted rows like this:

const {rowCount} = await db.query("update ...."); without needing a RETURNING statement.

specifically, the return type of Query in pg is

export interface QueryResult {
    command: string;
    rowCount: number;
    oid: number;
    rows: any[];
}

This library always returns only the rows property of that interface, which is mostly what you want, but in some cases getting the rowCount is useful (e.g. for performance of not having to return an array).

Add more known function invocations

e.g.

const distinct = (arg: s.Expression<any>) => new s.KnownFunctionInvocation("distinct", [arg], arg.type);
const min = (arg: s.Expression<any>) => new s.KnownFunctionInvocation("min", [arg], arg.type);
const max = (arg: s.Expression<any>) => new s.KnownFunctionInvocation("max", [arg], arg.type);
const nullif = (arg: s.Expression<any>, arg2: s.Expression<any>) => new s.KnownFunctionInvocation("nullif", [arg, arg2], arg.type);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.