922 lines
25 KiB
Markdown
922 lines
25 KiB
Markdown
<div align="center">
|
|
|
|
<img src="https://raw.githubusercontent.com/SeaQL/sea-query/master/docs/SeaQuery logo.png" width="280" alt="SeaQuery logo"/>
|
|
|
|
<p>
|
|
<strong>🔱 A dynamic query builder for MySQL, Postgres and SQLite</strong>
|
|
</p>
|
|
|
|
[](https://crates.io/crates/sea-query)
|
|
[](https://docs.rs/sea-query)
|
|
[](https://github.com/SeaQL/sea-query/actions/workflows/rust.yml)
|
|
|
|
</div>
|
|
|
|
## SeaQuery
|
|
|
|
SeaQuery is a query builder to help you construct dynamic SQL queries in Rust.
|
|
You can construct expressions, queries and schema as abstract syntax trees using an ergonomic API.
|
|
We support MySQL, Postgres and SQLite behind a common interface that aligns their behaviour where appropriate.
|
|
MS SQL Server Support is available under [SeaORM X](https://www.sea-ql.org/SeaORM-X/).
|
|
|
|
SeaQuery is written in 100% safe Rust. All workspace crates has `#![forbid(unsafe_code)]`.
|
|
|
|
SeaQuery is the foundation of [SeaORM](https://github.com/SeaQL/sea-orm), an async & dynamic ORM for Rust.
|
|
We provide integration for [SQLx](https://crates.io/crates/sqlx),
|
|
[postgres](https://crates.io/crates/postgres) and [rusqlite](https://crates.io/crates/rusqlite).
|
|
See [examples](https://github.com/SeaQL/sea-query/blob/master/examples) for usage.
|
|
|
|
[](https://github.com/SeaQL/sea-query/stargazers/)
|
|
If you like what we do, consider starring, commenting, sharing and contributing!
|
|
|
|
[](https://discord.com/invite/uCPdDXzbdv)
|
|
Join our Discord server to chat with others in the SeaQL community!
|
|
|
|
## Install
|
|
|
|
```toml
|
|
# Cargo.toml
|
|
[dependencies]
|
|
sea-query = "1.0"
|
|
```
|
|
|
|
SeaQuery is very lightweight, all dependencies are optional.
|
|
|
|
### Feature flags
|
|
|
|
Macro: `derive`
|
|
|
|
SQL engine: `backend-mysql`, `backend-postgres`, `backend-sqlite`
|
|
|
|
Type support: `with-chrono`, `with-time`, `with-json`, `with-rust_decimal`, `with-bigdecimal`, `with-uuid`,
|
|
`with-ipnetwork`, `with-mac_address`, `postgres-array`, `postgres-interval`, `postgres-vector`
|
|
|
|
## Usage
|
|
|
|
Table of Content
|
|
|
|
1. Basics
|
|
|
|
1. [Iden](#iden)
|
|
1. [Expression](#expression)
|
|
1. [Condition](#condition)
|
|
1. [Statement Builders](#statement-builders)
|
|
|
|
1. Query Statement
|
|
|
|
1. [Query Select](#query-select)
|
|
1. [Query Insert](#query-insert)
|
|
1. [Query Update](#query-update)
|
|
1. [Query Delete](#query-delete)
|
|
|
|
1. Advanced
|
|
1. [Aggregate Functions](#aggregate-functions)
|
|
1. [Casting](#casting)
|
|
1. [Custom Function](#custom-function)
|
|
|
|
1. Schema Statement
|
|
|
|
1. [Table Create](#table-create)
|
|
1. [Table Alter](#table-alter)
|
|
1. [Table Drop](#table-drop)
|
|
1. [Table Rename](#table-rename)
|
|
1. [Table Truncate](#table-truncate)
|
|
1. [Foreign Key Create](#foreign-key-create)
|
|
1. [Foreign Key Drop](#foreign-key-drop)
|
|
1. [Index Create](#index-create)
|
|
1. [Index Drop](#index-drop)
|
|
|
|
## Motivation
|
|
|
|
Why would you want to use a dynamic query builder?
|
|
|
|
### 1. Parameter bindings
|
|
|
|
One of the headaches when using raw SQL is parameter binding. With SeaQuery you can inject parameters
|
|
right alongside the expression, and the $N sequencing will be handled for you. No more "off by one" errors!
|
|
|
|
```rust
|
|
assert_eq!(
|
|
Query::select()
|
|
.expr(Expr::col("size_w").add(1).mul(2))
|
|
.from("glyph")
|
|
.and_where(Expr::col("image").like("A"))
|
|
.and_where(Expr::col("id").is_in([3, 4, 5]))
|
|
.build(PostgresQueryBuilder),
|
|
(
|
|
r#"SELECT ("size_w" + $1) * $2 FROM "glyph" WHERE "image" LIKE $3 AND "id" IN ($4, $5, $6)"#
|
|
.to_owned(),
|
|
Values(vec![
|
|
1.into(),
|
|
2.into(),
|
|
"A".to_owned().into(),
|
|
3.into(),
|
|
4.into(),
|
|
5.into(),
|
|
])
|
|
)
|
|
);
|
|
```
|
|
|
|
If you need an "escape hatch" to construct complex queries, you can use custom expressions,
|
|
and still have the benefit of sequentially-binded parameters.
|
|
|
|
```rust
|
|
assert_eq!(
|
|
Query::select()
|
|
.columns(["size_w", "size_h"])
|
|
.from("character")
|
|
.and_where(Expr::col("id").eq(1)) // this is $1
|
|
// custom expressions only need to define local parameter sequence.
|
|
// its global sequence will be re-written.
|
|
// here, we flip the order of $2 & $1 to make it look tricker!
|
|
.and_where(Expr::cust_with_values(r#""size_w" = $2 * $1"#, [3, 2]))
|
|
.and_where(Expr::col("size_h").gt(4)) // this is $N?
|
|
.build(PostgresQueryBuilder),
|
|
(
|
|
r#"SELECT "size_w", "size_h" FROM "character" WHERE "id" = $1 AND ("size_w" = $2 * $3) AND "size_h" > $4"#
|
|
.to_owned(),
|
|
Values(vec![1.into(), 2.into(), 3.into(), 4.into()])
|
|
)
|
|
);
|
|
```
|
|
|
|
### 2. Dynamic query
|
|
|
|
You can construct the query at runtime based on user inputs with a fluent interface,
|
|
so you don't have to append `WHERE` or `AND` conditionally.
|
|
|
|
```rust
|
|
fn query(a: Option<i32>, b: Option<char>) -> SelectStatement {
|
|
Query::select()
|
|
.column("id")
|
|
.from("character")
|
|
.apply_if(a, |q, v| {
|
|
q.and_where(Expr::col("font_id").eq(v));
|
|
})
|
|
.apply_if(b, |q, v| {
|
|
q.and_where(Expr::col("ascii").like(v));
|
|
})
|
|
.take()
|
|
}
|
|
|
|
assert_eq!(
|
|
query(Some(5), Some('A')).to_string(MysqlQueryBuilder),
|
|
"SELECT `id` FROM `character` WHERE `font_id` = 5 AND `ascii` LIKE 'A'"
|
|
);
|
|
assert_eq!(
|
|
query(Some(5), None).to_string(MysqlQueryBuilder),
|
|
"SELECT `id` FROM `character` WHERE `font_id` = 5"
|
|
);
|
|
assert_eq!(
|
|
query(None, None).to_string(MysqlQueryBuilder),
|
|
"SELECT `id` FROM `character`"
|
|
);
|
|
```
|
|
|
|
Conditions can be arbitrarily complex, thanks to SeaQuery's internal AST:
|
|
|
|
```rust
|
|
assert_eq!(
|
|
Query::select()
|
|
.column("id")
|
|
.from("glyph")
|
|
.cond_where(
|
|
Cond::any()
|
|
.add(
|
|
Cond::all()
|
|
.add(Expr::col("aspect").is_null())
|
|
.add(Expr::col("image").is_null())
|
|
)
|
|
.add(
|
|
Cond::all()
|
|
.add(Expr::col("aspect").is_in([3, 4]))
|
|
.add(Expr::col("image").like("A%"))
|
|
)
|
|
)
|
|
.to_string(PostgresQueryBuilder),
|
|
[
|
|
r#"SELECT "id" FROM "glyph""#,
|
|
r#"WHERE"#,
|
|
r#"("aspect" IS NULL AND "image" IS NULL)"#,
|
|
r#"OR"#,
|
|
r#"("aspect" IN (3, 4) AND "image" LIKE 'A%')"#,
|
|
]
|
|
.join(" ")
|
|
);
|
|
```
|
|
|
|
There is no superfluous parentheses `((((` cluttering the query, because SeaQuery respects
|
|
operator precedence when injecting them.
|
|
|
|
### 3. Cross database support
|
|
|
|
With SeaQuery, you can target multiple database backends while maintaining a single source of query logic.
|
|
|
|
```rust
|
|
let query = Query::insert()
|
|
.into_table("glyph")
|
|
.columns(["aspect", "image"])
|
|
.values_panic([
|
|
2.into(),
|
|
3.into(),
|
|
])
|
|
.on_conflict(
|
|
OnConflict::column("id")
|
|
.update_columns(["aspect", "image"])
|
|
.to_owned(),
|
|
)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `aspect` = VALUES(`aspect`), `image` = VALUES(`image`)"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") DO UPDATE SET "aspect" = "excluded"."aspect", "image" = "excluded"."image""#
|
|
);
|
|
```
|
|
|
|
### 4. Improved raw SQL ergonomics
|
|
|
|
SeaQuery 1.0 added a new `raw_query!` macro with named parameters, nested field access, array expansion and tuple expansion.
|
|
It surely will make crafting complex query easier.
|
|
|
|
```rust
|
|
let (a, b, c) = (1, 2, "A");
|
|
let d = vec![3, 4, 5];
|
|
let query = sea_query::raw_query!(
|
|
PostgresQueryBuilder,
|
|
r#"SELECT ("size_w" + {a}) * {b} FROM "glyph" WHERE "image" LIKE {c} AND "id" IN ({..d})"#
|
|
);
|
|
|
|
assert_eq!(
|
|
query.sql,
|
|
r#"SELECT ("size_w" + $1) * $2 FROM "glyph" WHERE "image" LIKE $3 AND "id" IN ($4, $5, $6)"#
|
|
);
|
|
assert_eq!(
|
|
query.values,
|
|
Values(vec![
|
|
1.into(),
|
|
2.into(),
|
|
"A".into(),
|
|
3.into(),
|
|
4.into(),
|
|
5.into()
|
|
])
|
|
);
|
|
```
|
|
|
|
Insert with vector-of-tuple expansion.
|
|
|
|
```rust
|
|
let values = vec![(2.1345, "24B"), (5.15, "12A")];
|
|
let query = sea_query::raw_query!(
|
|
PostgresQueryBuilder,
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES {..(values.0:1),}"#
|
|
);
|
|
|
|
assert_eq!(
|
|
query.sql,
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES ($1, $2), ($3, $4)"#
|
|
);
|
|
assert_eq!(
|
|
query.values,
|
|
Values(vec![2.1345.into(), "24B".into(), 5.15.into(), "12A".into()])
|
|
);
|
|
```
|
|
|
|
Update with nested field access.
|
|
|
|
```rust
|
|
struct Character {
|
|
id: i32,
|
|
font_size: u16,
|
|
}
|
|
let c = Character {
|
|
id: 11,
|
|
font_size: 22,
|
|
};
|
|
let query = sea_query::raw_query!(
|
|
MysqlQueryBuilder,
|
|
"UPDATE `character` SET `font_size` = {c.font_size} WHERE `id` = {c.id}"
|
|
);
|
|
|
|
assert_eq!(
|
|
query.sql,
|
|
"UPDATE `character` SET `font_size` = ? WHERE `id` = ?"
|
|
);
|
|
assert_eq!(query.values, Values(vec![22u16.into(), 11i32.into()]));
|
|
```
|
|
|
|
## Basics
|
|
|
|
### Iden
|
|
|
|
`Iden` is a trait for identifiers used in any query statement.
|
|
|
|
Commonly implemented by Enum where each Enum represents a table found in a database,
|
|
and its variants include table name and column name.
|
|
|
|
You can use the `Iden` derive macro to implement it.
|
|
|
|
```rust
|
|
#[derive(Iden)]
|
|
enum Character {
|
|
Table,
|
|
Id,
|
|
FontId,
|
|
FontSize,
|
|
}
|
|
|
|
assert_eq!(Character::Table.to_string(), "character");
|
|
assert_eq!(Character::Id.to_string(), "id");
|
|
assert_eq!(Character::FontId.to_string(), "font_id");
|
|
assert_eq!(Character::FontSize.to_string(), "font_size");
|
|
|
|
#[derive(Iden)]
|
|
struct Glyph;
|
|
assert_eq!(Glyph.to_string(), "glyph");
|
|
```
|
|
|
|
```rust
|
|
use sea_query::{Iden, enum_def};
|
|
|
|
#[enum_def]
|
|
struct Character {
|
|
pub foo: u64,
|
|
}
|
|
|
|
// It generates the following along with Iden impl
|
|
enum CharacterIden {
|
|
Table,
|
|
Foo,
|
|
}
|
|
|
|
assert_eq!(CharacterIden::Table.to_string(), "character");
|
|
assert_eq!(CharacterIden::Foo.to_string(), "foo");
|
|
```
|
|
|
|
### Expression
|
|
|
|
Use [`Expr`] constructors and [`ExprTrait`] methods
|
|
to construct `SELECT`, `JOIN`, `WHERE` and `HAVING` expression in query.
|
|
|
|
```rust
|
|
assert_eq!(
|
|
Query::select()
|
|
.column("char_code")
|
|
.from("character")
|
|
.and_where(
|
|
Expr::col("size_w")
|
|
.add(1)
|
|
.mul(2)
|
|
.eq(Expr::col("size_h").div(2).sub(1))
|
|
)
|
|
.and_where(
|
|
Expr::col("size_w").in_subquery(
|
|
Query::select()
|
|
.expr(Expr::cust_with_values("ln($1 ^ $2)", [2.4, 1.2]))
|
|
.take()
|
|
)
|
|
)
|
|
.and_where(
|
|
Expr::col("char_code")
|
|
.like("D")
|
|
.and(Expr::col("char_code").like("E"))
|
|
)
|
|
.to_string(PostgresQueryBuilder),
|
|
[
|
|
r#"SELECT "char_code" FROM "character""#,
|
|
r#"WHERE ("size_w" + 1) * 2 = ("size_h" / 2) - 1"#,
|
|
r#"AND "size_w" IN (SELECT ln(2.4 ^ 1.2))"#,
|
|
r#"AND ("char_code" LIKE 'D' AND "char_code" LIKE 'E')"#,
|
|
]
|
|
.join(" ")
|
|
);
|
|
```
|
|
|
|
### Condition
|
|
|
|
If you have complex conditions to express, you can use the [`Condition`] builder,
|
|
usable for [`ConditionalStatement::cond_where`] and [`SelectStatement::cond_having`].
|
|
|
|
```rust
|
|
assert_eq!(
|
|
Query::select()
|
|
.column("id")
|
|
.from("glyph")
|
|
.cond_where(
|
|
Cond::any()
|
|
.add(
|
|
Cond::all()
|
|
.add(Expr::col("aspect").is_null())
|
|
.add(Expr::col("image").is_null())
|
|
)
|
|
.add(
|
|
Cond::all()
|
|
.add(Expr::col("aspect").is_in([3, 4]))
|
|
.add(Expr::col("image").like("A%"))
|
|
)
|
|
)
|
|
.to_string(PostgresQueryBuilder),
|
|
[
|
|
r#"SELECT "id" FROM "glyph""#,
|
|
r#"WHERE"#,
|
|
r#"("aspect" IS NULL AND "image" IS NULL)"#,
|
|
r#"OR"#,
|
|
r#"("aspect" IN (3, 4) AND "image" LIKE 'A%')"#,
|
|
]
|
|
.join(" ")
|
|
);
|
|
```
|
|
|
|
There is also the [`any!`] and [`all!`] macro at your convenience:
|
|
|
|
```rust
|
|
Query::select().cond_where(any![
|
|
Expr::col(Glyph::Aspect).is_in([3, 4]),
|
|
all![
|
|
Expr::col(Glyph::Aspect).is_null(),
|
|
Expr::col(Glyph::Image).like("A%")
|
|
]
|
|
]);
|
|
```
|
|
|
|
### Statement Builders
|
|
|
|
Statements are divided into 2 categories: Query and Schema, and to be serialized into SQL
|
|
with [`QueryStatementBuilder`] and [`SchemaStatementBuilder`] respectively.
|
|
|
|
Schema statement has the following interface:
|
|
|
|
```rust
|
|
fn build<T: SchemaBuilder>(&self, schema_builder: T) -> String;
|
|
```
|
|
|
|
Query statement has the following interfaces:
|
|
|
|
```rust
|
|
fn build<T: QueryBuilder>(&self, query_builder: T) -> (String, Values);
|
|
|
|
fn to_string<T: QueryBuilder>(&self, query_builder: T) -> String;
|
|
```
|
|
|
|
`build` builds a SQL statement as string and parameters to be passed to the database driver
|
|
through the binary protocol. This is the preferred way as it has less overhead and is more secure.
|
|
|
|
`to_string` builds a SQL statement as string with parameters injected. This is good for testing
|
|
and debugging.
|
|
|
|
## Query Statement
|
|
|
|
### Query Select
|
|
|
|
```rust
|
|
let query = Query::select()
|
|
.column("char_code")
|
|
.column(("font", "name"))
|
|
.from("character")
|
|
.left_join("font", Expr::col(("character", "font_id")).equals(("font", "id")))
|
|
.and_where(Expr::col("size_w").is_in([3, 4]))
|
|
.and_where(Expr::col("char_code").like("A%"))
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"SELECT `char_code`, `font`.`name` FROM `character` LEFT JOIN `font` ON `character`.`font_id` = `font`.`id` WHERE `size_w` IN (3, 4) AND `char_code` LIKE 'A%'"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"SELECT "char_code", "font"."name" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id" WHERE "size_w" IN (3, 4) AND "char_code" LIKE 'A%'"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"SELECT "char_code", "font"."name" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id" WHERE "size_w" IN (3, 4) AND "char_code" LIKE 'A%'"#
|
|
);
|
|
```
|
|
|
|
### Query Insert
|
|
|
|
```rust
|
|
let query = Query::insert()
|
|
.into_table(Glyph::Table)
|
|
.columns([Glyph::Aspect, Glyph::Image])
|
|
.values_panic([5.15.into(), "12A".into()])
|
|
.values_panic([4.21.into(), "123".into()])
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (5.15, '12A'), (4.21, '123')"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (5.15, '12A'), (4.21, '123')"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (5.15, '12A'), (4.21, '123')"#
|
|
);
|
|
```
|
|
|
|
### Query Update
|
|
|
|
```rust
|
|
let query = Query::update()
|
|
.table(Glyph::Table)
|
|
.values([(Glyph::Aspect, 1.23.into()), (Glyph::Image, "123".into())])
|
|
.and_where(Expr::col(Glyph::Id).eq(1))
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"UPDATE `glyph` SET `aspect` = 1.23, `image` = '123' WHERE `id` = 1"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"UPDATE "glyph" SET "aspect" = 1.23, "image" = '123' WHERE "id" = 1"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"UPDATE "glyph" SET "aspect" = 1.23, "image" = '123' WHERE "id" = 1"#
|
|
);
|
|
```
|
|
|
|
### Query Delete
|
|
|
|
```rust
|
|
let query = Query::delete()
|
|
.from_table(Glyph::Table)
|
|
.cond_where(
|
|
Cond::any()
|
|
.add(Expr::col(Glyph::Id).lt(1))
|
|
.add(Expr::col(Glyph::Id).gt(10)),
|
|
)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"DELETE FROM `glyph` WHERE `id` < 1 OR `id` > 10"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"DELETE FROM "glyph" WHERE "id" < 1 OR "id" > 10"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"DELETE FROM "glyph" WHERE "id" < 1 OR "id" > 10"#
|
|
);
|
|
```
|
|
|
|
## Advanced
|
|
|
|
### Aggregate Functions
|
|
|
|
`max`, `min`, `sum`, `avg`, `count` etc
|
|
|
|
```rust
|
|
let query = Query::select()
|
|
.expr(Func::sum(Expr::col((Char::Table, Char::SizeH))))
|
|
.from(Char::Table)
|
|
.to_owned();
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"SELECT SUM(`character`.`size_h`) FROM `character`"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"SELECT SUM("character"."size_h") FROM "character""#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"SELECT SUM("character"."size_h") FROM "character""#
|
|
);
|
|
```
|
|
|
|
### Casting
|
|
|
|
```rust
|
|
let query = Query::select()
|
|
.expr(Func::cast_as("hello", "MyType"))
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"SELECT CAST('hello' AS MyType)"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"SELECT CAST('hello' AS MyType)"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"SELECT CAST('hello' AS MyType)"#
|
|
);
|
|
```
|
|
|
|
### Custom Function
|
|
|
|
```rust
|
|
struct MyFunction;
|
|
|
|
impl Iden for MyFunction {
|
|
fn unquoted(&self) -> &str {
|
|
"MY_FUNCTION"
|
|
}
|
|
}
|
|
|
|
let query = Query::select()
|
|
.expr(Func::cust(MyFunction).arg(Expr::val("hello")))
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
query.to_string(MysqlQueryBuilder),
|
|
r#"SELECT MY_FUNCTION('hello')"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(PostgresQueryBuilder),
|
|
r#"SELECT MY_FUNCTION('hello')"#
|
|
);
|
|
assert_eq!(
|
|
query.to_string(SqliteQueryBuilder),
|
|
r#"SELECT MY_FUNCTION('hello')"#
|
|
);
|
|
```
|
|
|
|
## Schema Statement
|
|
|
|
### Table Create
|
|
|
|
```rust
|
|
let table = Table::create()
|
|
.table("character")
|
|
.if_not_exists()
|
|
.col(ColumnDef::new("id").integer().not_null().auto_increment().primary_key())
|
|
.col(ColumnDef::new("font_size").integer().not_null())
|
|
.col(ColumnDef::new("character").string().not_null())
|
|
.col(ColumnDef::new("size_w").integer().not_null())
|
|
.col(ColumnDef::new("size_h").integer().not_null())
|
|
.col(ColumnDef::new("font_id").integer().default(Expr::val(1)))
|
|
.foreign_key(
|
|
ForeignKey::create()
|
|
.name("character_fk")
|
|
.from("character", "font_id")
|
|
.to("font", "id")
|
|
.on_delete(ForeignKeyAction::Cascade)
|
|
.on_update(ForeignKeyAction::Cascade)
|
|
)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
table.to_string(MysqlQueryBuilder),
|
|
[
|
|
r#"CREATE TABLE IF NOT EXISTS `character` ("#,
|
|
r#"`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,"#,
|
|
r#"`font_size` int NOT NULL,"#,
|
|
r#"`character` varchar(255) NOT NULL,"#,
|
|
r#"`size_w` int NOT NULL,"#,
|
|
r#"`size_h` int NOT NULL,"#,
|
|
r#"`font_id` int DEFAULT 1,"#,
|
|
r#"CONSTRAINT `character_fk`"#,
|
|
r#"FOREIGN KEY (`font_id`) REFERENCES `font` (`id`)"#,
|
|
r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
|
|
r#")"#,
|
|
].join(" ")
|
|
);
|
|
assert_eq!(
|
|
table.to_string(PostgresQueryBuilder),
|
|
[
|
|
r#"CREATE TABLE IF NOT EXISTS "character" ("#,
|
|
r#""id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL PRIMARY KEY,"#,
|
|
r#""font_size" integer NOT NULL,"#,
|
|
r#""character" varchar NOT NULL,"#,
|
|
r#""size_w" integer NOT NULL,"#,
|
|
r#""size_h" integer NOT NULL,"#,
|
|
r#""font_id" integer DEFAULT 1,"#,
|
|
r#"CONSTRAINT "character_fk""#,
|
|
r#"FOREIGN KEY ("font_id") REFERENCES "font" ("id")"#,
|
|
r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
|
|
r#")"#,
|
|
].join(" ")
|
|
);
|
|
assert_eq!(
|
|
table.to_string(SqliteQueryBuilder),
|
|
[
|
|
r#"CREATE TABLE IF NOT EXISTS "character" ("#,
|
|
r#""id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,"#,
|
|
r#""font_size" integer NOT NULL,"#,
|
|
r#""character" varchar NOT NULL,"#,
|
|
r#""size_w" integer NOT NULL,"#,
|
|
r#""size_h" integer NOT NULL,"#,
|
|
r#""font_id" integer DEFAULT 1,"#,
|
|
r#"FOREIGN KEY ("font_id") REFERENCES "font" ("id") ON DELETE CASCADE ON UPDATE CASCADE"#,
|
|
r#")"#,
|
|
].join(" ")
|
|
);
|
|
```
|
|
|
|
### Table Alter
|
|
|
|
```rust
|
|
let table = Table::alter()
|
|
.table(Font::Table)
|
|
.add_column(ColumnDef::new("new_col").integer().not_null().default(100))
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
table.to_string(MysqlQueryBuilder),
|
|
r#"ALTER TABLE `font` ADD COLUMN `new_col` int NOT NULL DEFAULT 100"#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(PostgresQueryBuilder),
|
|
r#"ALTER TABLE "font" ADD COLUMN "new_col" integer NOT NULL DEFAULT 100"#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(SqliteQueryBuilder),
|
|
r#"ALTER TABLE "font" ADD COLUMN "new_col" integer NOT NULL DEFAULT 100"#,
|
|
);
|
|
```
|
|
|
|
### Table Drop
|
|
|
|
```rust
|
|
let table = Table::drop()
|
|
.table(Glyph::Table)
|
|
.table(Char::Table)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
table.to_string(MysqlQueryBuilder),
|
|
r#"DROP TABLE `glyph`, `character`"#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(PostgresQueryBuilder),
|
|
r#"DROP TABLE "glyph", "character""#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(SqliteQueryBuilder),
|
|
r#"DROP TABLE "glyph", "character""#
|
|
);
|
|
```
|
|
|
|
### Table Rename
|
|
|
|
```rust
|
|
let table = Table::rename().table(Font::Table, "font_new").to_owned();
|
|
|
|
assert_eq!(
|
|
table.to_string(MysqlQueryBuilder),
|
|
r#"RENAME TABLE `font` TO `font_new`"#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(PostgresQueryBuilder),
|
|
r#"ALTER TABLE "font" RENAME TO "font_new""#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(SqliteQueryBuilder),
|
|
r#"ALTER TABLE "font" RENAME TO "font_new""#
|
|
);
|
|
```
|
|
|
|
### Table Truncate
|
|
|
|
```rust
|
|
let table = Table::truncate().table(Font::Table).to_owned();
|
|
|
|
assert_eq!(
|
|
table.to_string(MysqlQueryBuilder),
|
|
r#"TRUNCATE TABLE `font`"#
|
|
);
|
|
assert_eq!(
|
|
table.to_string(PostgresQueryBuilder),
|
|
r#"TRUNCATE TABLE "font""#
|
|
);
|
|
// Sqlite does not support the TRUNCATE statement
|
|
```
|
|
|
|
### Foreign Key Create
|
|
|
|
```rust
|
|
let foreign_key = ForeignKey::create()
|
|
.name("FK_character_font")
|
|
.from(Char::Table, Char::FontId)
|
|
.to(Font::Table, Font::Id)
|
|
.on_delete(ForeignKeyAction::Cascade)
|
|
.on_update(ForeignKeyAction::Cascade)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
foreign_key.to_string(MysqlQueryBuilder),
|
|
[
|
|
r#"ALTER TABLE `character`"#,
|
|
r#"ADD CONSTRAINT `FK_character_font`"#,
|
|
r#"FOREIGN KEY (`font_id`) REFERENCES `font` (`id`)"#,
|
|
r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
|
|
]
|
|
.join(" ")
|
|
);
|
|
assert_eq!(
|
|
foreign_key.to_string(PostgresQueryBuilder),
|
|
[
|
|
r#"ALTER TABLE "character" ADD CONSTRAINT "FK_character_font""#,
|
|
r#"FOREIGN KEY ("font_id") REFERENCES "font" ("id")"#,
|
|
r#"ON DELETE CASCADE ON UPDATE CASCADE"#,
|
|
]
|
|
.join(" ")
|
|
);
|
|
// Sqlite does not support modification of foreign key constraints to existing tables
|
|
```
|
|
|
|
### Foreign Key Drop
|
|
|
|
```rust
|
|
let foreign_key = ForeignKey::drop()
|
|
.name("FK_character_font")
|
|
.table(Char::Table)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
foreign_key.to_string(MysqlQueryBuilder),
|
|
r#"ALTER TABLE `character` DROP FOREIGN KEY `FK_character_font`"#
|
|
);
|
|
assert_eq!(
|
|
foreign_key.to_string(PostgresQueryBuilder),
|
|
r#"ALTER TABLE "character" DROP CONSTRAINT "FK_character_font""#
|
|
);
|
|
// Sqlite does not support modification of foreign key constraints to existing tables
|
|
```
|
|
|
|
### Index Create
|
|
|
|
```rust
|
|
let index = Index::create()
|
|
.name("idx-glyph-aspect")
|
|
.table(Glyph::Table)
|
|
.col(Glyph::Aspect)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
index.to_string(MysqlQueryBuilder),
|
|
r#"CREATE INDEX `idx-glyph-aspect` ON `glyph` (`aspect`)"#
|
|
);
|
|
assert_eq!(
|
|
index.to_string(PostgresQueryBuilder),
|
|
r#"CREATE INDEX "idx-glyph-aspect" ON "glyph" ("aspect")"#
|
|
);
|
|
assert_eq!(
|
|
index.to_string(SqliteQueryBuilder),
|
|
r#"CREATE INDEX "idx-glyph-aspect" ON "glyph" ("aspect")"#
|
|
);
|
|
```
|
|
|
|
### Index Drop
|
|
|
|
```rust
|
|
let index = Index::drop()
|
|
.name("idx-glyph-aspect")
|
|
.table(Glyph::Table)
|
|
.to_owned();
|
|
|
|
assert_eq!(
|
|
index.to_string(MysqlQueryBuilder),
|
|
r#"DROP INDEX `idx-glyph-aspect` ON `glyph`"#
|
|
);
|
|
assert_eq!(
|
|
index.to_string(PostgresQueryBuilder),
|
|
r#"DROP INDEX "idx-glyph-aspect""#
|
|
);
|
|
assert_eq!(
|
|
index.to_string(SqliteQueryBuilder),
|
|
r#"DROP INDEX "idx-glyph-aspect""#
|
|
);
|
|
```
|
|
|
|
## License
|
|
|
|
Licensed under either of
|
|
|
|
- Apache License, Version 2.0
|
|
([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
|
|
- MIT license
|
|
([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
|
|
|
at your option.
|
|
|
|
## Contribution
|
|
|
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
|
dual licensed as above, without any additional terms or conditions.
|
|
|
|
SeaQuery is a community driven project. We welcome you to participate, contribute and together build for Rust's future.
|
|
|
|
A big shout out to our contributors:
|
|
|
|
[](https://github.com/SeaQL/sea-query/graphs/contributors)
|