Perry native package: Prisma ORM backed by sqlx + PostgreSQL.
Drop-in replacement for @prisma/client using sqlx with PostgreSQL. Sibling of perry-prisma (MySQL) and perry-sqlite — same architecture, ~95% identical code.
TypeScript → JSON string → FFI → Rust dispatcher → sqlx (PostgreSQL) → JSON result → TypeScript
native/src/lib.rs— All Rust logic: query builder, row-to-JSON conversion, FFI exportsnative/build.rs— Readsprisma/schema.prismaat build time, generatesgenerated_models.rswith model metadatanative/Cargo.toml— Crate config; sqlx withpostgresfeaturesrc/index.ts— TypeScript layer:PrismaClient,ModelClient, type definitions, FFI declarationsperry.config.ts— Perry compiler configurationpackage.json— Package metadata, FFI function declarations, platform lib requirements
PRISMA_SCHEMA_PATHenv var../../../chainblick/api/prisma/schema.prisma(relative tonative/)- Falls back to empty model list with compile warning
- Pool type:
PgPool(notMySqlPool) - Identifier quoting:
"col"double quotes (not`col`backticks) - Placeholders:
$1, $2, $3...viaParamCounter(not?) — this is the main complexity difference ParamCounterstruct: Threaded throughbuild_whereand all query-building functions as&mut ParamCounter; callpc.next()to get the next$Nplaceholder- INSERT returning: Uses
RETURNING *to get the inserted row directly (no separate SELECT afterlast_insert_id()) - Transactions:
BEGIN/COMMIT/ROLLBACK(notSET autocommit=0; START TRANSACTION) - Boolean binding:
q.bind(*b)native bool (notq.bind(*b as i8)) - Skip duplicates:
ON CONFLICT DO NOTHING(notINSERT IGNORE) - Unlimited offset:
OFFSET nwithout LIMIT (notLIMIT 18446744073709551615 OFFSET n) - TLS libs: Same as perry-prisma — needs
Security/SystemConfigurationon iOS, full crypto libs on Windows
All exported as postgres_*:
| Function | Signature |
|---|---|
postgres_connect |
(config_json: StringHeader*) -> Promise* |
postgres_disconnect |
(handle: i64) -> Promise* |
postgres_execute |
(handle: i64, query_json: StringHeader*) -> Promise* |
postgres_transaction_begin |
(handle: i64) -> Promise* |
postgres_transaction_end |
(tx_handle: i64, options_json: StringHeader*) -> Promise* |
# Host check
cd native && cargo check
# iOS cross-compile check
cd native && cargo check --target aarch64-apple-ios
# Perry check (from package root)
perry checkThe ParamCounter is the key difference from the MySQL and SQLite siblings. Every function that generates SQL with bound parameters must accept &mut ParamCounter:
struct ParamCounter { n: usize }
impl ParamCounter {
fn new() -> Self { Self { n: 0 } }
fn next(&mut self) -> String { self.n += 1; format!("${}", self.n) }
}Functions that use it:
build_where(where_val, model_def, params, pc)— 4th arg added vs MySQL/SQLitefind_many,delete_record,delete_many,update_record,update_many,count_records— all create a localParamCounter::new()create_record,create_many— also useParamCounterfor INSERT placeholders
When adding new query operations, always create a fresh ParamCounter::new() at the top of the function and pass &mut pc into build_where and any placeholder generation.
- Adding a new query operation: Add handler in
execute_querymatch, implement async fn taking&PgPool. CreateParamCounter::new()and thread&mut pcthrough all SQL generation. - Changing SQL dialect: All SQL generation is in
build_where,build_order_by,build_select_cols, and the operation functions — grep forformat!with SQL strings - Syncing with perry-prisma: Diff
native/src/lib.rsagainst perry-prisma's version; apply non-dialect changes, remembering to add&mut pcparameter to any newbuild_wherecalls