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
12 changes: 12 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ walkdir = "2.5.0"
clap = { version = "4.6.1", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149" }
sqlparser = { version = "0.61.0" }
sqlparser = { version = "0.61.0", features = ["visitor"] }
regex = { version = "1.12.3" }
convert_case = "0.11.0"
colored = "3.1.1"
Expand Down
6 changes: 6 additions & 0 deletions book/docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ The page aims to list down limitations of SQLX-TS. So for the users who are inte
### 1. parsing of SQL is done using sqlparser-rs and any bugs in this modules would be inherited

[sqlparser-rs](https://github.com/sqlparser-rs/sqlparser-rs) is an essential module of SQLX-TS in order to process SQLs into Typescript type definitions. As a result, any bug in this module will be inherited to sqlx-ts and we will need an update in the module in order to fix the problem. So far, sqlparser-rs is well maintained and being updated in the recent days.

### 2. tables with the same name from different schemas cannot be used in the same query

Columns of a table are looked up by its name, so a query that uses tables with the same name from different schemas
or databases, e.g. `staff.announcements` with `public.announcements`, cannot tell them apart. SQLX-TS warns about it
and reads the columns of the first qualified table. See [qualified table names](/type-generation#qualified-table-names).
23 changes: 23 additions & 0 deletions book/docs/type-generation/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,29 @@ the type name by setting the `@name` annotation, you can read more in the sectio

### Annotations for overrides

### Qualified table names

Tables can be qualified with a schema in PostgreSQL, or with a database in MySQL. SQLX-TS reads the columns of the
table from that schema or database instead of the default one of the connection.

```typescript
// PostgreSQL: reads columns of the `announcements` table in the `staff` schema
const announcements = sql`SELECT message FROM staff.announcements WHERE message = $1`

// MySQL: reads columns of the `announcements` table in the `staff` database,
// even if `DB_NAME` of the connection is another database
const announcements = sql`SELECT message FROM staff.announcements WHERE message = ?`
```

- Unqualified tables are read from the `public` schema in PostgreSQL and from the `DB_NAME` database in MySQL
- For `database.schema.table` names in PostgreSQL, the schema is used
- In SQLite, tables can be qualified with `main`, e.g. `main.items`
- The database user of the connection needs access to the schema or database. `information_schema` only lists
tables the user has privileges on, so a table without access is reported as having no columns
- A query cannot use two tables with the same name from different schemas yet, e.g. `staff.announcements` with
`public.announcements`, or `staff.announcements` with an unqualified `announcements`. SQLX-TS warns about it and
reads the columns of the first qualified table, so the types of the other table may be incorrect

## Q & A

##### Why doesn't SQLx support named parameterised queries? `e.g. SELECT * FROM items WHERE point = :point`
Expand Down
8 changes: 8 additions & 0 deletions playpen/db/mysql_migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,11 @@ CREATE TABLE type_mapping_test (
big_id BIGINT UNSIGNED NOT NULL,
amount BIGINT NOT NULL
);

CREATE DATABASE IF NOT EXISTS staff;

CREATE TABLE staff.announcements (
id INT PRIMARY KEY AUTO_INCREMENT,
message VARCHAR(1000),
priority ENUM('low', 'high') NOT NULL
);
8 changes: 8 additions & 0 deletions playpen/db/mysql_migration_5_6.sql
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,11 @@ CREATE TABLE type_mapping_test (
big_id BIGINT UNSIGNED NOT NULL,
amount BIGINT NOT NULL
);

CREATE DATABASE IF NOT EXISTS staff;

CREATE TABLE staff.announcements (
id INT PRIMARY KEY AUTO_INCREMENT,
message VARCHAR(1000),
priority ENUM('low', 'high') NOT NULL
);
10 changes: 10 additions & 0 deletions playpen/db/postgres_migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,13 @@ INSERT INTO json_test_data (name, data, metadata) VALUES
('analytics',
'{"date": "2024-01-15", "metrics": {"visitors": 1500, "pageViews": 4500, "bounceRate": 0.35, "sources": {"organic": 850, "direct": 400, "referral": 250}}}',
'{"source": "analytics", "version": "1.0"}');

CREATE SCHEMA staff;

CREATE TYPE staff.priority_enum AS ENUM ('low', 'high');

CREATE TABLE staff.announcements (
id SERIAL PRIMARY KEY,
message VARCHAR(1000),
priority staff.priority_enum NOT NULL
);
23 changes: 20 additions & 3 deletions src/ts_generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use std::{fs, path::Path};
use super::annotations::extract_param_annotations;

use crate::common::lazy::CONFIG;
use crate::common::logger::*;
use crate::common::SQL;
use crate::core::connection::DBConn;
use crate::ts_generator::annotations::extract_result_annotations;
use crate::ts_generator::information_schema::{collect_table_schemas, with_table_schemas};
use crate::ts_generator::sql_parser::translate_stmt::translate_stmt;
use crate::ts_generator::types::ts_query::TsQuery;

Expand Down Expand Up @@ -183,10 +185,25 @@ pub async fn generate_ts_interface(sql: &SQL, db_conn: &DBConn) -> Result<TsQuer
let annotated_param_types = extract_param_annotations(sql.query.as_str());
ts_query.set_annotated_params(annotated_param_types);

for sql_statement in &sql_ast {
// The loot level statements cannot have any alias
translate_stmt(&mut ts_query, sql_statement, None, db_conn).await?;
let table_schemas = collect_table_schemas(&sql_ast);
for ambiguous_table in &table_schemas.ambiguous_tables {
warning!(
"Table '{}' is referenced as {} in query {}. sqlx-ts cannot tell tables with the same name apart yet, so it uses {}.{} and the generated types may be incorrect",
ambiguous_table.table_name,
ambiguous_table.references.join(" and "),
ts_query.name,
ambiguous_table.resolved_schema,
ambiguous_table.table_name
);
}
with_table_schemas(table_schemas.schemas, async {
for sql_statement in &sql_ast {
// The loot level statements cannot have any alias
translate_stmt(&mut ts_query, sql_statement, None, db_conn).await?;
}
Ok::<(), TsGeneratorError>(())
})
.await?;

Ok(ts_query)
}
Loading
Loading