Skip to content

Commit

Permalink
feat(js-connectors): add support for "last insert id" in ResultSet (#…
Browse files Browse the repository at this point in the history
…4124)

* feat(js-connectors): enable last_insert_id support in Rust

* feat(js-connectors): enable lastInsertId in TypeScript, fix panic on prisma.model.create for autoincrement columns in PlanetScale

* chore(js-connectors): add smoke tests for createOne with autoincrement columns
  • Loading branch information
jkomyno committed Aug 9, 2023
1 parent 5dfedd1 commit ca2cd60
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 21 deletions.
3 changes: 1 addition & 2 deletions quaint/src/connector/result_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ impl ResultSet {
}
}

#[cfg(any(feature = "sqlite", feature = "mysql"))]
pub(crate) fn set_last_insert_id(&mut self, id: u64) {
pub fn set_last_insert_id(&mut self, id: u64) {
self.last_insert_id = Some(id);
}

Expand Down
19 changes: 19 additions & 0 deletions query-engine/js-connectors/js/js-connector-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,28 @@ import { ColumnTypeEnum } from './const'
export type ColumnType = typeof ColumnTypeEnum[keyof typeof ColumnTypeEnum]

export interface ResultSet {
/**
* List of column types appearing in a database query, in the same order as `columnNames`.
* They are used within the Query Engine to convert values from JS to Quaint values.
*/
columnTypes: Array<ColumnType>

/**
* List of column names appearing in a database query, in the same order as `columnTypes`.
*/
columnNames: Array<string>

/**
* List of rows retrieved from a database query.
* Each row is a list of values, whose length matches `columnNames` and `columnTypes`.
*/
rows: Array<Array<any>>

/**
* The last ID of an `INSERT` statement, if any.
* This is required for `AUTO_INCREMENT` columns in MySQL and SQLite-flavoured databases.
*/
lastInsertId?: string
}

export interface Query {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,14 @@ class PrismaPlanetScale implements Connector, Closeable {
const tag = '[js::query_raw]'
debug(`${tag} %O`, query)

const { fields, rows: results } = await this.performIO(query)
const { fields, insertId: lastInsertId, rows: results } = await this.performIO(query)

const columns = fields.map(field => field.name)
const resultSet: ResultSet = {
columnNames: columns,
columnTypes: fields.map(field => fieldToColumnType(field.type as PlanetScaleColumnType)),
rows: results.map(result => columns.map(column => result[column])),
lastInsertId,
}

return resultSet
Expand Down
30 changes: 15 additions & 15 deletions query-engine/js-connectors/js/pnpm-lock.yaml

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

4 changes: 2 additions & 2 deletions query-engine/js-connectors/js/smoke-test-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
"@jkomyno/prisma-js-connector-utils": "workspace:*",
"@jkomyno/prisma-neon-js-connector": "workspace:*",
"@jkomyno/prisma-planetscale-js-connector": "workspace:*",
"@prisma/client": "5.1.0"
"@prisma/client": "5.2.0-dev.30"
},
"devDependencies": {
"prisma": "^5.1.0",
"prisma": "5.2.0-dev.30",
"tsx": "^3.12.7"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,12 @@ model Parent {
@@unique([p_1, p_2])
}

model Author {
id Int @id @default(autoincrement())
firstName String
lastName String
age Int
@@map("authors")
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,12 @@ enum type_test_enum_column_null {
value2
value3
}

model Author {
id Int @id @default(autoincrement())
firstName String
lastName String
age Int
@@map("authors")
}
40 changes: 40 additions & 0 deletions query-engine/js-connectors/js/smoke-test-js/src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function smokeTest(db: Connector & Closeable, prismaSchemaRelativeP
const test = new SmokeTest(engine, db.flavour)

await test.testFindManyTypeTest()
await test.createAutoIncrement()
await test.testCreateAndDeleteChildParent()

// Note: calling `engine.disconnect` won't actually close the database connection.
Expand Down Expand Up @@ -124,6 +125,45 @@ class SmokeTest {
return resultSet
}

async createAutoIncrement() {
await this.engine.query(`
{
"modelName": "Author",
"action": "deleteMany",
"query": {
"arguments": {
"where": {}
},
"selection": {
"count": true
}
}
}
`, 'trace', undefined)

const author = await this.engine.query(`
{
"modelName": "Author",
"action": "createOne",
"query": {
"arguments": {
"data": {
"firstName": "Firstname from autoincrement",
"lastName": "Lastname from autoincrement",
"age": 99
}
},
"selection": {
"id": true,
"firstName": true,
"lastName": true
}
}
}
`, 'trace', undefined)
console.log('[nodejs] author', JSON.stringify(JSON.parse(author), null, 2))
}

async testCreateAndDeleteChildParent() {
/* Delete all child and parent records */

Expand Down
14 changes: 13 additions & 1 deletion query-engine/js-connectors/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub struct JSResultSet {
pub column_names: Vec<String>,
// Note this might be encoded differently for performance reasons
pub rows: Vec<Vec<serde_json::Value>>,
pub last_insert_id: Option<String>,
}

impl JSResultSet {
Expand Down Expand Up @@ -288,6 +289,7 @@ impl From<JSResultSet> for QuaintResultSet {
rows,
column_names,
column_types,
last_insert_id,
} = js_result_set;

let quaint_rows = rows
Expand All @@ -301,7 +303,17 @@ impl From<JSResultSet> for QuaintResultSet {
})
.collect::<Vec<_>>();

QuaintResultSet::new(column_names, quaint_rows)
let last_insert_id = last_insert_id.and_then(|id| id.parse::<u64>().ok());
let mut quaint_result_set = QuaintResultSet::new(column_names, quaint_rows);

// Not a fan of this (extracting the `Some` value from an `Option` and pass it to a method that creates a new `Some` value),
// but that's Quaint's ResultSet API and that's how the MySQL connector does it.
// Sqlite, on the other hand, uses a `last_insert_id.unwrap_or(0)` approach.
if let Some(last_insert_id) = last_insert_id {
quaint_result_set.set_last_insert_id(last_insert_id);
}

quaint_result_set
}
}

Expand Down

0 comments on commit ca2cd60

Please sign in to comment.