Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# main

- Auto-escape all ReScript keywords in generated record field names.
- Add automatic parsing of PostgreSQL check constraints to generate ReScript polyvariant types for enumeration-style constraints. Supports both `column IN (value1, value2, ...)` and `column = ANY (ARRAY[value1, value2, ...])` patterns with string and integer values.
- Remove dependency on `@rescript/core` since it's not really used.

Expand Down
29 changes: 28 additions & 1 deletion packages/cli/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,34 @@ function toRescriptName(name: string): string {
return `${name[0]?.toLowerCase() ?? ''}${name.slice(1)}`;
}

const reservedReScriptWords = ['type'];
const reservedReScriptWords = [
'and',
'as',
'assert',
'await',
'constraint',
'else',
'exception',
'external',
'false',
'for',
'if',
'in',
'include',
'let',
'module',
'mutable',
'of',
'open',
'private',
'rec',
'switch',
'true',
'try',
'type',
'when',
'while',
];

function getFieldName(fieldName: string): string {
if (reservedReScriptWords.includes(fieldName)) {
Expand Down
31 changes: 31 additions & 0 deletions packages/example/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,37 @@ CREATE TABLE dump(
json_test jsonb
);

-- Commented out fields are already Postgres keywords
create table rescript_keywords_need_to_be_escaped(
id SERIAL PRIMARY KEY,
-- and text,
-- as text,
assert text,
await text,
-- constraint text,
-- else text,
exception text,
external text,
-- false text,
-- for text,
if text,
-- in text,
include text,
let text,
module text,
mutable text,
of text,
open text,
private text,
rec text,
switch text,
-- true text,
try text,
type text,
-- when text,
while text
);

INSERT INTO users (email, user_name, first_name, last_name, age)
VALUES ('[email protected]', 'alexd', 'Alex', 'Doe', 35),
('[email protected]', 'jane67', 'Jane', 'Holmes', 23),
Expand Down
4 changes: 4 additions & 0 deletions packages/example/src/books/Keywords.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
let query = %sql.one(`
/* @name Keywords */
select * from rescript_keywords_need_to_be_escaped
`)
61 changes: 61 additions & 0 deletions packages/example/src/books/Keywords__sql.gen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* TypeScript file generated from Keywords__sql.res by genType. */

/* eslint-disable */
/* tslint:disable */

const Keywords__sqlJS = require('./Keywords__sql.js');

import type {Pg_Client_t as PgTyped_Pg_Client_t} from 'pgtyped-rescript/src/res/PgTyped.gen';

/** 'Keywords' parameters type */
export type keywordsParams = void;

/** 'Keywords' return type */
export type keywordsResult = {
readonly assert: (undefined | string);
readonly await: (undefined | string);
readonly exception: (undefined | string);
readonly external: (undefined | string);
readonly id: number;
readonly if: (undefined | string);
readonly include: (undefined | string);
readonly let: (undefined | string);
readonly module: (undefined | string);
readonly mutable: (undefined | string);
readonly of: (undefined | string);
readonly open: (undefined | string);
readonly private: (undefined | string);
readonly rec: (undefined | string);
readonly switch: (undefined | string);
readonly try: (undefined | string);
readonly type: (undefined | string);
readonly while: (undefined | string)
};

/** 'Keywords' query type */
export type keywordsQuery = { readonly params: keywordsParams; readonly result: keywordsResult };

/** Returns an array of all matched results. */
export const Keywords_many: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<keywordsResult[]> = Keywords__sqlJS.Keywords.many as any;

/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */
export const Keywords_one: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<(undefined | keywordsResult)> = Keywords__sqlJS.Keywords.one as any;

/** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */
export const Keywords_expectOne: (_1:PgTyped_Pg_Client_t, _2:keywordsParams, errorMessage:(undefined | string)) => Promise<keywordsResult> = Keywords__sqlJS.Keywords.expectOne as any;

/** Executes the query, but ignores whatever is returned by it. */
export const Keywords_execute: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<void> = Keywords__sqlJS.Keywords.execute as any;

export const keywords: (params:keywordsParams, client:PgTyped_Pg_Client_t) => Promise<keywordsResult[]> = Keywords__sqlJS.keywords as any;

export const Keywords: {
/** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */
expectOne: (_1:PgTyped_Pg_Client_t, _2:keywordsParams, errorMessage:(undefined | string)) => Promise<keywordsResult>;
/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */
one: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<(undefined | keywordsResult)>;
/** Returns an array of all matched results. */
many: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<keywordsResult[]>;
/** Executes the query, but ignores whatever is returned by it. */
execute: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<void>
} = Keywords__sqlJS.Keywords as any;
98 changes: 98 additions & 0 deletions packages/example/src/books/Keywords__sql.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/** Types generated for queries found in "src/books/Keywords.res" */
open PgTyped


/** 'Keywords' parameters type */
@gentype
type keywordsParams = unit

/** 'Keywords' return type */
@gentype
type keywordsResult = {
@as("assert") assert_: option<string>,
@as("await") await_: option<string>,
@as("exception") exception_: option<string>,
@as("external") external_: option<string>,
id: int,
@as("if") if_: option<string>,
@as("include") include_: option<string>,
@as("let") let_: option<string>,
@as("module") module_: option<string>,
@as("mutable") mutable_: option<string>,
@as("of") of_: option<string>,
@as("open") open_: option<string>,
@as("private") private_: option<string>,
@as("rec") rec_: option<string>,
@as("switch") switch_: option<string>,
@as("try") try_: option<string>,
@as("type") type_: option<string>,
@as("while") while_: option<string>,
}

/** 'Keywords' query type */
@gentype
type keywordsQuery = {
params: keywordsParams,
result: keywordsResult,
}

%%private(let keywordsIR: IR.t = %raw(`{"usedParamSet":{},"params":[],"statement":"select * from rescript_keywords_need_to_be_escaped"}`))

/**
Runnable query:
```sql
select * from rescript_keywords_need_to_be_escaped
```

*/
@gentype
module Keywords: {
/** Returns an array of all matched results. */
@gentype
let many: (PgTyped.Pg.Client.t, keywordsParams) => promise<array<keywordsResult>>
/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */
@gentype
let one: (PgTyped.Pg.Client.t, keywordsParams) => promise<option<keywordsResult>>

/** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */
@gentype
let expectOne: (
PgTyped.Pg.Client.t,
keywordsParams,
~errorMessage: string=?
) => promise<keywordsResult>

/** Executes the query, but ignores whatever is returned by it. */
@gentype
let execute: (PgTyped.Pg.Client.t, keywordsParams) => promise<unit>
} = {
@module("pgtyped-rescript-runtime") @new external keywords: IR.t => PreparedStatement.t<keywordsParams, keywordsResult> = "PreparedQuery";
let query = keywords(keywordsIR)
let query = (params, ~client) => query->PreparedStatement.run(params, ~client)

@gentype
let many = (client, params) => query(params, ~client)

@gentype
let one = async (client, params) => switch await query(params, ~client) {
| [item] => Some(item)
| _ => None
}

@gentype
let expectOne = async (client, params, ~errorMessage=?) => switch await query(params, ~client) {
| [item] => item
| _ => panic(errorMessage->Option.getOr("More or less than one item was returned"))
}

@gentype
let execute = async (client, params) => {
let _ = await query(params, ~client)
}
}

@gentype
@deprecated("Use 'Keywords.many' directly instead")
let keywords = (params, ~client) => Keywords.many(client, params)