Skip to content

Commit b5a7c1c

Browse files
committed
sqlite tests
1 parent 2c2b4b5 commit b5a7c1c

23 files changed

Lines changed: 544 additions & 16 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
SQLx-ts is a CLI application featuring compile-time checked queries without a DSL and generates types against SQLs to keep your code type-safe
1212

1313
- **Compile time checked queries** - never ship a broken SQL query to production (and [sqlx-ts is not an ORM](https://github.com/JasonShin/sqlx-ts#sqlx-ts-is-not-an-orm))
14-
- **TypeScript type generations** - generates type definitions based on the raw SQLs and you can use them with any MySQL or PostgreSQL driver
15-
- **Database Agnostic** - support for [PostgreSQL](http://postgresql.org/) and [MySQL](https://www.mysql.com/) (and more DB supports to come)
14+
- **TypeScript type generations** - generates type definitions based on the raw SQLs and you can use them with any MySQL, PostgreSQL, or SQLite driver
15+
- **Database Agnostic** - support for [PostgreSQL](http://postgresql.org/), [MySQL](https://www.mysql.com/), and [SQLite](https://www.sqlite.org/)
1616
- **TypeScript and JavaScript** - supports for both [TypeScript](https://jasonshin.github.io/sqlx-ts/reference-guide/4.typescript-types-generation.html) and [JavaScript](https://github.com/JasonShin/sqlx-ts#using-sqlx-ts-in-vanilla-javascript)
1717

1818
<br>

book/docs/connect/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ $ sqlx-ts <path> --db-type postgres --db-url postgres://user:pass@localhost:5432
4646
$ sqlx-ts <path> --db-type mysql --db-url mysql://user:pass@localhost:3306/mydb
4747
```
4848

49+
#### SQLite
50+
51+
For SQLite, you only need to provide the database file path. No host, port, or user credentials are required:
52+
53+
```bash
54+
$ sqlx-ts <path> --db-type sqlite --db-name ./mydb.sqlite
55+
```
56+
4957
**Note:** When `--db-url` is provided, it takes precedence over individual connection parameters (`--db-host`, `--db-port`, `--db-user`, `--db-pass`, `--db-name`).
5058

5159
Run the following command for more details:

book/docs/connect/config-file.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ Alternatively, you can use `DB_URL` to specify the connection string directly:
6666
}
6767
```
6868

69+
For SQLite, only `DB_TYPE` and `DB_NAME` (the file path) are required:
70+
71+
```json
72+
{
73+
"generate_types": {
74+
"enabled": true
75+
},
76+
"connections": {
77+
"default": {
78+
"DB_TYPE": "sqlite",
79+
"DB_NAME": "./mydb.sqlite"
80+
}
81+
}
82+
}
83+
```
84+
6985
## Configuration options
7086

7187
### connections (required)
@@ -92,7 +108,7 @@ const postgresSQL = sql`
92108

93109
Supported fields of each connection include
94110
- `DB_URL`: Database connection URL (e.g. `postgres://user:pass@host:port/dbname` or `mysql://user:pass@host:port/dbname`). If provided, this overrides individual connection parameters (`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`)
95-
- `DB_TYPE`: type of database connection (mysql | postgres)
111+
- `DB_TYPE`: type of database connection (mysql | postgres | sqlite)
96112
- `DB_USER`: database user name
97113
- `DB_PASS`: database password
98114
- `DB_HOST`: database host (e.g. 127.0.0.1)

book/docs/connect/environment-variables.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
| DB_HOST | Primary DB host |
88
| DB_PASS | Primary DB password |
99
| DB_PORT | Primary DB port number |
10-
| DB_TYPE | Type of primary database to connect [default: postgres] [possible values: postgres, mysql] |
10+
| DB_TYPE | Type of primary database to connect [default: postgres] [possible values: postgres, mysql, sqlite] |
1111
| DB_USER | Primary DB user name |
1212
| DB_NAME | Primary DB name |
1313
| PG_SEARCH_PATH | PostgreSQL schema search path (default is "$user,public") [https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH](https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH) |
@@ -47,3 +47,14 @@ sqlx-ts <path>
4747

4848
**Note:** When `DB_URL` is set, it takes precedence over individual connection parameters (`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME`).
4949

50+
### SQLite
51+
52+
For SQLite, only `DB_TYPE` and `DB_NAME` (file path) are required:
53+
54+
```bash
55+
export DB_TYPE=sqlite
56+
export DB_NAME=./mydb.sqlite
57+
58+
sqlx-ts <path>
59+
```
60+

playpen/db/sqlite_migration.sql

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
-- SQLite Migration
2+
-- This migration creates the same tables as the PostgreSQL and MySQL migrations
3+
-- but using SQLite-compatible syntax.
4+
5+
-- Factions Table
6+
CREATE TABLE IF NOT EXISTS factions (
7+
id INTEGER PRIMARY KEY AUTOINCREMENT,
8+
name TEXT UNIQUE NOT NULL,
9+
description TEXT
10+
);
11+
12+
-- Races Table
13+
CREATE TABLE IF NOT EXISTS races (
14+
id INTEGER PRIMARY KEY AUTOINCREMENT,
15+
name TEXT UNIQUE NOT NULL,
16+
faction_id INTEGER REFERENCES factions(id) ON DELETE CASCADE
17+
);
18+
19+
-- Classes Table
20+
CREATE TABLE IF NOT EXISTS classes (
21+
id INTEGER PRIMARY KEY AUTOINCREMENT,
22+
name TEXT UNIQUE NOT NULL,
23+
specialization TEXT
24+
);
25+
26+
-- Characters Table
27+
CREATE TABLE IF NOT EXISTS characters (
28+
id INTEGER PRIMARY KEY AUTOINCREMENT,
29+
name TEXT NOT NULL,
30+
race_id INTEGER REFERENCES races(id),
31+
class_id INTEGER REFERENCES classes(id),
32+
level INTEGER DEFAULT 1,
33+
experience INTEGER DEFAULT 0,
34+
gold REAL DEFAULT 0,
35+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
36+
);
37+
38+
-- Guilds Table
39+
CREATE TABLE IF NOT EXISTS guilds (
40+
id INTEGER PRIMARY KEY AUTOINCREMENT,
41+
name TEXT UNIQUE NOT NULL,
42+
description TEXT,
43+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
44+
);
45+
46+
-- Guild Members Table
47+
CREATE TABLE IF NOT EXISTS guild_members (
48+
guild_id INTEGER REFERENCES guilds(id) ON DELETE CASCADE,
49+
character_id INTEGER REFERENCES characters(id) ON DELETE CASCADE,
50+
rank TEXT,
51+
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
52+
PRIMARY KEY (guild_id, character_id)
53+
);
54+
55+
-- Inventory Table
56+
CREATE TABLE IF NOT EXISTS inventory (
57+
id INTEGER PRIMARY KEY AUTOINCREMENT,
58+
character_id INTEGER REFERENCES characters(id) ON DELETE CASCADE,
59+
quantity INTEGER DEFAULT 1
60+
);
61+
62+
-- Items Table
63+
CREATE TABLE IF NOT EXISTS items (
64+
id INTEGER PRIMARY KEY AUTOINCREMENT,
65+
name TEXT NOT NULL,
66+
rarity TEXT,
67+
flavor_text TEXT,
68+
inventory_id INTEGER REFERENCES inventory(id) ON DELETE CASCADE
69+
);
70+
71+
-- Quests Table
72+
CREATE TABLE IF NOT EXISTS quests (
73+
id INTEGER PRIMARY KEY AUTOINCREMENT,
74+
name TEXT NOT NULL,
75+
description TEXT,
76+
rewards TEXT,
77+
completed BOOLEAN DEFAULT 0,
78+
required_level INTEGER DEFAULT 1
79+
);
80+
81+
-- Character Quests Table
82+
CREATE TABLE IF NOT EXISTS character_quests (
83+
character_id INTEGER REFERENCES characters(id) ON DELETE CASCADE,
84+
quest_id INTEGER REFERENCES quests(id) ON DELETE CASCADE,
85+
status TEXT DEFAULT 'In Progress',
86+
PRIMARY KEY (character_id, quest_id)
87+
);
88+
89+
-- Random types table for testing SQLite type mappings
90+
CREATE TABLE IF NOT EXISTS random (
91+
int1 INTEGER,
92+
real1 REAL,
93+
text1 TEXT,
94+
blob1 BLOB,
95+
numeric1 NUMERIC,
96+
bool1 BOOLEAN,
97+
date1 DATE,
98+
datetime1 DATETIME,
99+
float1 FLOAT,
100+
double1 DOUBLE,
101+
varchar1 VARCHAR(100),
102+
char1 CHAR(10),
103+
json1 JSON
104+
);
105+
106+
--- SEED DATA
107+
108+
INSERT INTO factions (name, description) VALUES
109+
('alliance', 'The noble and righteous faction'),
110+
('horde', 'The fierce and battle-hardened faction');
111+
112+
INSERT INTO races (name, faction_id) VALUES
113+
('human', 1),
114+
('night elf', 1),
115+
('dwarf', 1),
116+
('gnome', 1),
117+
('orc', 2),
118+
('troll', 2),
119+
('tauren', 2),
120+
('undead', 2);
121+
122+
INSERT INTO classes (name, specialization) VALUES
123+
('warrior', '{"role": "tank", "weapon": "sword", "abilities": ["charge", "slam", "shield block"]}'),
124+
('hunter', '{"role": "ranged", "weapon": "bow", "abilities": ["aimed shot", "multi-shot", "trap"]}');

src/core/connection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use color_eyre::Result;
1818
use swc_common::errors::Handler;
1919

2020
/// Enum to hold a specific database connection instance
21+
#[allow(clippy::enum_variant_names)]
2122
pub enum DBConn {
2223
MySQLPooledConn(Mutex<Pool<MySqlConnectionManager>>),
2324
PostgresConn(Mutex<Pool<PostgresConnectionManager>>),

src/ts_generator/information_schema.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,18 +246,12 @@ impl DBSchema {
246246
Err(_) => continue,
247247
};
248248

249-
for row in rows {
250-
if let Ok((field_name, field_type, notnull, tbl_name)) = row {
251-
let field = Field {
252-
field_type: TsFieldType::get_ts_field_type_from_sqlite_field_type(
253-
field_type,
254-
tbl_name,
255-
field_name.clone(),
256-
),
257-
is_nullable: !notnull,
258-
};
259-
all_fields.insert(field_name, field);
260-
}
249+
for (field_name, field_type, notnull, tbl_name) in rows.flatten() {
250+
let field = Field {
251+
field_type: TsFieldType::get_ts_field_type_from_sqlite_field_type(field_type, tbl_name, field_name.clone()),
252+
is_nullable: !notnull,
253+
};
254+
all_fields.insert(field_name, field);
261255
}
262256
}
263257

tests/demo_happy_path.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod demo_happy_path_tests {
77
use std::fs;
88
use std::io::Write;
99
use std::path::Path;
10+
use tempfile::tempdir;
1011
use walkdir::WalkDir;
1112

1213
fn run_demo_test(demo_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
@@ -283,4 +284,80 @@ mod demo_happy_path_tests {
283284

284285
Ok(())
285286
}
287+
288+
#[test]
289+
fn all_demo_sqlite_should_pass() -> Result<(), Box<dyn std::error::Error>> {
290+
let root_path = current_dir().unwrap();
291+
let demo_path = root_path.join("tests/demo_sqlite");
292+
let migration_path = root_path.join("playpen/db/sqlite_migration.sql");
293+
294+
// Create a temporary SQLite database and run the migration
295+
let tmp_dir = tempdir()?;
296+
let db_path = tmp_dir.path().join("demo_test.db");
297+
let conn = rusqlite::Connection::open(&db_path)?;
298+
let migration_sql = fs::read_to_string(&migration_path)?;
299+
conn.execute_batch(&migration_sql)?;
300+
drop(conn);
301+
302+
// Create a temporary config file pointing to the SQLite database
303+
let config_path = tmp_dir.path().join(".sqlxrc.json");
304+
let config_content = format!(
305+
r#"{{
306+
"generateTypes": {{
307+
"enabled": true
308+
}},
309+
"connections": {{
310+
"default": {{
311+
"DB_TYPE": "sqlite",
312+
"DB_NAME": "{}"
313+
}}
314+
}}
315+
}}"#,
316+
db_path.display()
317+
);
318+
fs::write(&config_path, &config_content)?;
319+
320+
// Run sqlx-ts against the demo_sqlite directory
321+
let mut cmd = cargo_bin_cmd!("sqlx-ts");
322+
cmd
323+
.arg(demo_path.to_str().unwrap())
324+
.arg("--ext=ts")
325+
.arg(format!("--config={}", config_path.display()))
326+
.arg("-g");
327+
328+
cmd
329+
.assert()
330+
.success()
331+
.stdout(predicates::str::contains("No SQL errors detected!"));
332+
333+
// Verify all generated types match snapshots
334+
for entry in WalkDir::new(&demo_path) {
335+
if entry.is_ok() {
336+
let entry = entry.unwrap();
337+
let path = entry.path();
338+
let parent = entry.path().parent().unwrap();
339+
let file_name = path.file_name().unwrap().to_str().unwrap().to_string();
340+
341+
if path.is_file() && file_name.ends_with(".queries.ts") {
342+
let base_file_name = file_name.split('.').collect::<Vec<&str>>();
343+
let base_file_name = base_file_name.first().unwrap();
344+
let snapshot_path = parent.join(format!("{base_file_name}.snapshot.ts"));
345+
346+
let generated_types = fs::read_to_string(path)?;
347+
348+
if !snapshot_path.exists() {
349+
let mut snapshot_file = fs::File::create(&snapshot_path)?;
350+
writeln!(snapshot_file, "{generated_types}")?;
351+
}
352+
353+
assert_eq!(
354+
generated_types.trim().to_string().trim(),
355+
fs::read_to_string(&snapshot_path)?.to_string().trim(),
356+
)
357+
}
358+
}
359+
}
360+
361+
Ok(())
362+
}
286363
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export type DeleteItemParams = [number];
2+
3+
export interface IDeleteItemResult {
4+
5+
}
6+
7+
export interface IDeleteItemQuery {
8+
params: DeleteItemParams;
9+
result: IDeleteItemResult;
10+
}
11+
12+
export type DeleteCharacterParams = [number];
13+
14+
export interface IDeleteCharacterResult {
15+
16+
}
17+
18+
export interface IDeleteCharacterQuery {
19+
params: DeleteCharacterParams;
20+
result: IDeleteCharacterResult;
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export type DeleteItemParams = [number];
2+
3+
export interface IDeleteItemResult {
4+
5+
}
6+
7+
export interface IDeleteItemQuery {
8+
params: DeleteItemParams;
9+
result: IDeleteItemResult;
10+
}
11+
12+
export type DeleteCharacterParams = [number];
13+
14+
export interface IDeleteCharacterResult {
15+
16+
}
17+
18+
export interface IDeleteCharacterQuery {
19+
params: DeleteCharacterParams;
20+
result: IDeleteCharacterResult;
21+
}
22+

0 commit comments

Comments
 (0)