Skip to content

Commit

Permalink
update to fix mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
TristenHarr committed Jun 10, 2024
1 parent 6087a7a commit 6bae967
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 23 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ This changelog documents changes between release tags.
## [Unreleased]
Upcoming changes for the next versioned release.

## [0.0.15]
* Fix mutations

## [0.0.14]
* Change orderBy to use default casing. (Ordering is case-sensitive and uses underlying implementation which differs from Postgres)

Expand Down
4 changes: 2 additions & 2 deletions connector-definition/connector-metadata.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
packagingDefinition:
type: PrebuiltDockerImage
dockerImage: ghcr.io/hasura/ndc-turso:v0.0.14
dockerImage: ghcr.io/hasura/ndc-turso:v0.0.15
supportedEnvironmentVariables:
- name: TURSO_URL
description: The url for the Turso database
Expand All @@ -9,7 +9,7 @@ supportedEnvironmentVariables:
commands:
update:
type: Dockerized
dockerImage: ghcr.io/hasura/ndc-turso:v0.0.14
dockerImage: ghcr.io/hasura/ndc-turso:v0.0.15
commandArgs:
- update
dockerComposeWatch:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ndc-turso",
"version": "0.0.14",
"version": "0.0.15",
"main": "index.js",
"author": "Tristen Harr",
"scripts": {
Expand Down
8 changes: 8 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ export const SCALAR_TYPES: { [key: string]: ScalarType } = {
},
},
}
},
Boolean: {
aggregate_functions: {},
comparison_operators: {
_eq: {
type: "equal"
}
}
}
};

Expand Down
84 changes: 71 additions & 13 deletions src/handlers/mutation.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,79 @@
import { Conflict, Forbidden, MutationOperation, MutationOperationResults, MutationRequest, MutationResponse, NotSupported } from "@hasura/ndc-sdk-typescript";
import { Conflict, Forbidden, MutationOperation, MutationOperationResults, MutationRequest, MutationResponse, NestedField } from "@hasura/ndc-sdk-typescript";
import { Configuration, State } from "..";
import { InArgs, InStatement, ResultSet } from "@libsql/client/.";
import { format } from "sql-formatter";

async function execute_sql_transaction(state: State, statements: InStatement[]): Promise<ResultSet[]> {
const results = state.client.batch(statements, "write");
return results;
}

function buildInsertSql(table: string, data: any[]): [string, any[]] {
const formatSQLWithArgs = (sql: string, args: any[]): string => {
let index = 0;
return sql.replace(/\?/g, () => {
const arg = args[index++];
if (typeof arg === 'string') {
return `'${arg}'`;
} else if (arg === null) {
return 'NULL';
} else {
return arg;
}
});
};

function buildReturningClause(returnFields: NestedField | null | undefined): string {
if (!returnFields || !returnFields.fields) {
return '*'; // Default to returning all columns if returnFields is not provided
}

const extractColumns = (fields: { [key: string]: any }): string[] => {
return Object.entries(fields).map(([fieldKey, fieldValue]) => {
if (fieldValue.type === 'column') {
return `"${fieldValue.column}" AS "${fieldKey}"`;
}
return null;
}).filter(Boolean) as string[];
};

if (returnFields.type === 'array' && returnFields.fields?.type === 'object') {
const columns = extractColumns(returnFields.fields.fields);
return columns.join(", ");
}

if (returnFields.type === 'object') {
const columns = extractColumns(returnFields.fields);
return columns.join(", ");
}

return '*'; // Default case
}

function buildInsertSql(
table: string,
data: any[],
returnFields: NestedField | null | undefined): [string, any[]] {
if (data.length === 0) {
return ["", []];
}

const columns = Object.keys(data[0]).map(col => `"${col}"`).join(", ");
const placeholders = "(" + Object.keys(data[0]).map(() => "?").join(", ") + ")";
const valuesTuple = Array(data.length).fill(placeholders).join(", ");
const sql = `INSERT INTO "${table}" (${columns}) VALUES ${valuesTuple} RETURNING *`; // Always include RETURNING *
const returning = buildReturningClause(returnFields);
const sql = `INSERT INTO "${table}" (${columns}) VALUES ${valuesTuple} RETURNING ${returning}`; // Always include RETURNING *
const values: InArgs[] = data.reduce((acc, item) => acc.concat(Object.values(item)), []);

return [sql, values];
}

function buildUpdateSql(table: string, pkColumns: { [key: string]: any }, setArguments: { [key: string]: any }, incArguments: { [key: string]: any }): [string, any[]] {
function buildUpdateSql(
table: string,
pkColumns: { [key: string]: any },
setArguments: { [key: string]: any },
incArguments: { [key: string]: any },
returnFields: NestedField | null | undefined
): [string, any[]] {
const setClauses: string[] = [];
const args: any[] = [];

Expand All @@ -41,14 +93,19 @@ function buildUpdateSql(table: string, pkColumns: { [key: string]: any }, setArg
args.push(...Object.values(pkColumns));

const setClause = setClauses.join(", ");
const sql = `UPDATE "${table}" SET ${setClause} WHERE ${whereClause} RETURNING *`;
const returning = buildReturningClause(returnFields);
const sql = `UPDATE "${table}" SET ${setClause} WHERE ${whereClause} RETURNING ${returning}`;
return [sql, args];
}

function buildDeleteSql(table: string, pkColumns: { [key: string]: any }): [string, any[]] {
function buildDeleteSql(
table: string,
pkColumns: { [key: string]: any },
returnFields: NestedField | null | undefined): [string, any[]] {
const whereClause = Object.keys(pkColumns).map(column => `"${column}" = ?`).join(" AND ");
const args: InArgs[] = Object.values(pkColumns);
const sql = `DELETE FROM "${table}" WHERE ${whereClause} RETURNING *`;
const returning = buildReturningClause(returnFields);
const sql = `DELETE FROM "${table}" WHERE ${whereClause} RETURNING ${returning}`;
return [sql, args];
}

Expand Down Expand Up @@ -79,7 +136,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
if (procedure.name.startsWith("insert_") && procedure.name.endsWith("_one")){
const table: string = procedure.name.slice("insert_".length, -"_one".length);
const data = [procedure.arguments.object];
const [sql, values] = buildInsertSql(table, data);
const [sql, values] = buildInsertSql(table, data, procedure.fields);

if (sql) {
try {
Expand All @@ -101,7 +158,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
} else if (procedure.name.startsWith("delete_") && procedure.name.endsWith("_by_pk")) {
const table: string = procedure.name.slice("delete_".length, -"_by_pk".length);
const pkColumns = procedure.arguments.pk_columns as { [key: string]: any; };
const [sql, values] = buildDeleteSql(table, pkColumns);
const [sql, values] = buildDeleteSql(table, pkColumns, procedure.fields);
try {
const results: ResultSet[] = await execute_sql_transaction(state, [{ sql, args: values }]);
const deleteResult = results[0]; // Assuming batch operation returns an array of ResultSet
Expand All @@ -119,7 +176,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
const pkColumns = procedure.arguments.pk_columns as { [key: string]: any; };
const setArguments = procedure.arguments._set || {};
const incArguments = procedure.arguments._inc || {};
const [sql, values] = buildUpdateSql(table, pkColumns, setArguments, incArguments);
const [sql, values] = buildUpdateSql(table, pkColumns, setArguments, incArguments, procedure.fields);

try {
const results: ResultSet[] = await execute_sql_transaction(state, [{ sql, args: values }]);
Expand All @@ -146,7 +203,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
const statements: InStatement[] = pkColumnsArray.map((pkColumns, index) => {
const setArguments = setArray[index] || {};
const incArguments = incArray[index] || {};
const [sql, args] = buildUpdateSql(table, pkColumns, setArguments, incArguments);
const [sql, args] = buildUpdateSql(table, pkColumns, setArguments, incArguments, procedure.fields);
return { sql, args };
});

Expand All @@ -167,7 +224,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu

// Create a batch of delete statements
const statements: InStatement[] = pkColumnsArray.map(pkColumns => {
const [sql, args] = buildDeleteSql(table, pkColumns);
const [sql, args] = buildDeleteSql(table, pkColumns, procedure.fields);
return { sql, args };
});

Expand All @@ -190,7 +247,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
throw new Forbidden("No data provided for insert_many operation.", {});
}

const [sql, values] = buildInsertSql(table, data);
const [sql, values] = buildInsertSql(table, data, procedure.fields);

try {
const results: ResultSet[] = await execute_sql_transaction(state, [{ sql, args: values }]);
Expand All @@ -211,6 +268,7 @@ export async function do_mutation(configuration: Configuration, state: State, mu
}
}
}

return {
operation_results: operation_results
};
Expand Down
123 changes: 118 additions & 5 deletions src/handlers/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,61 @@ function is_numeric_type(type_name: string): boolean {
return numeric_types.includes(type_name);
}

function get_field_operators(field_type: string): Record<string, string> {
if (field_type === 'Int' || field_type === 'Float') {
return {
"_eq": field_type,
"_neq": field_type,
"_gt": field_type,
"_lt": field_type,
"_gte": field_type,
"_lte": field_type,
"_is_null": "Boolean"
};
} else if (field_type === 'String') {
return {
"_eq": field_type,
"_neq": field_type,
"_like": field_type,
"_is_null": "Boolean"
};
}
return {};
}

function get_nested_where(cn: string, new_object_fields: { [fieldName: string]: ObjectField }, nested: number = 1): ObjectType {
if (nested === 0) {
return {
description: `Where for ${cn}`,
fields: { ...new_object_fields }
};
}
return {
description: `Where for ${cn}`,
fields: {
...new_object_fields,
"_not": {
type: {
type: "nullable",
underlying_type: { type: "named", name: `list_${cn}_bool_exp${'_nested'.repeat(nested)}` }
}
},
"_or": {
type: {
type: "nullable",
underlying_type: { type: "array", element_type: { type: "named", name: `list_${cn}_bool_exp${'_nested'.repeat(nested)}` } }
}
},
"_and": {
type: {
type: "nullable",
underlying_type: { type: "array", element_type: { type: "named", name: `list_${cn}_bool_exp${'_nested'.repeat(nested)}` } }
}
}
}
};
}

export function do_get_schema(configuration: Configuration): SchemaResponse {
const { config } = configuration;
if (!config) {
Expand Down Expand Up @@ -50,12 +105,33 @@ export function do_get_schema(configuration: Configuration): SchemaResponse {
foreign_keys: foreign_keys
});

const new_object_fields: { [fieldName: string]: ObjectField } = {};
const insert_object_fields: { [fieldName: string]: ObjectField } = {};

const new_object_fields: { [fieldName: string]: ObjectField} = {};
field_details.field_names.forEach(fieldName => {
const field_type = field_details.field_types[fieldName];
const operators = get_field_operators(field_type);
const field_input_type_name = `${field_type}_comparison_exp`;

if (!object_types[field_input_type_name]) {
object_types[field_input_type_name] = {
description: `Input type for filtering on field '${fieldName}'`,
fields: Object.fromEntries(Object.entries(operators).map(([op, operator_type]) => [
op, { type: { type: "nullable", underlying_type: { type: "named", name: operator_type } } }
]))
};
}

new_object_fields[fieldName] = {
type: {
type: "nullable",
underlying_type: { type: "named", name: field_input_type_name }
}
};

const isNullable = field_details.nullable_keys.includes(fieldName);
if (isNullable){
new_object_fields[fieldName] = {
if (isNullable) {
insert_object_fields[fieldName] = {
type: {
type: "nullable",
underlying_type: {
Expand All @@ -65,18 +141,55 @@ export function do_get_schema(configuration: Configuration): SchemaResponse {
}
};
} else {
new_object_fields[fieldName] = {
insert_object_fields[fieldName] = {
type: {
type: "named",
name: field_details.field_types[fieldName]
}
};
}
});

object_types[`${cn}_InsertType`] = {
description: `Insert type for ${cn}`,
fields: new_object_fields,
fields: insert_object_fields,
};

// object_types[`list_${cn}_bool_exp_nested_nested_nested`] = get_nested_where(cn, new_object_fields, 0);
// object_types[`list_${cn}_bool_exp_nested_nested`] = get_nested_where(cn, new_object_fields, 3);
// object_types[`list_${cn}_bool_exp_nested`] = get_nested_where(cn, new_object_fields, 2);
// object_types[`list_${cn}_bool_exp`] = get_nested_where(cn, new_object_fields, 1);

// const listProcedure: ProcedureInfo = {
// name: `list_${cn}`,
// description: `List records from the ${cn} collection.`,
// arguments: {
// "limit": {
// type: {
// type: "nullable",
// underlying_type: { type: "named", name: "Int" }
// }
// },
// "offset": {
// type: {
// type: "nullable",
// underlying_type: { type: "named", name: "Int" }
// }
// },
// "where": {
// type: {
// type: "nullable",
// underlying_type: { type: "named", name: `list_${cn}_bool_exp` }
// }
// }
// },
// result_type: {
// type: "array",
// element_type: { type: "named", name: `${cn}_InsertType` }
// }
// };
// procedures.push(listProcedure);

const insertOneProcedure: ProcedureInfo = {
name: `insert_${cn}_one`,
description: `Insert a single record into the ${cn} collection.`,
Expand Down

0 comments on commit 6bae967

Please sign in to comment.