Merge branch 'master' into jk_cli-list-migrations

This commit is contained in:
Sean Griffin 2017-04-20 16:55:54 -04:00 committed by GitHub
commit 1aed718dcf
27 changed files with 475 additions and 26 deletions

View File

@ -43,7 +43,7 @@ matrix:
allow_failures:
- rust: nightly
include:
- rust: nightly-2017-03-20
- rust: nightly-2017-04-19
env: CLIPPY=YESPLEASE
script:
- (cd diesel && cargo rustc --no-default-features --features "lint unstable chrono serde_json uuid sqlite postgres mysql" -- -Zno-trans)

View File

@ -6,10 +6,14 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
## Unreleased
### Added
* Added the `migration list` command to Diesel CLI for listing all available migrations and marking those that have been applied.
* Added support for adding two nullable columns.
## [0.12.0] - 2016-03-16
### Added

View File

@ -14,12 +14,12 @@ categories = ["database"]
[dependencies]
byteorder = "1.0"
chrono = { version = "0.3", optional = true }
clippy = { optional = true, version = "=0.0.121" }
libsqlite3-sys = { version = ">=0.7.1, <0.8.0", optional = true }
clippy = { optional = true, version = "=0.0.125" }
libsqlite3-sys = { version = ">=0.8.0, <0.9.0", optional = true, features = ["min_sqlite_version_3_7_16"] }
mysqlclient-sys = { version = ">=0.1.0, <0.3.0", optional = true }
pq-sys = { version = ">=0.3.0, <0.5.0", optional = true }
quickcheck = { version = "0.3.1", optional = true }
serde_json = { version = ">=0.8.0, <0.10.0", optional = true }
serde_json = { version = ">=0.8.0, <2.0", optional = true }
time = { version = "0.1", optional = true }
url = { version = "1.4.0", optional = true }
uuid = { version = ">=0.2.0, <0.5.0", optional = true, features = ["use_std"] }
@ -27,7 +27,7 @@ uuid = { version = ">=0.2.0, <0.5.0", optional = true, features = ["use_std"] }
[dev-dependencies]
cfg-if = "0.1.0"
diesel_codegen = "0.12.0"
dotenv = "0.8.0"
dotenv = ">=0.8, <0.10"
quickcheck = "0.3.1"
tempdir = "^0.3.4"

View File

@ -43,6 +43,10 @@
//!
//! [Identifiable]: trait.Identifiable.html
//!
//! If the name of your foreign key doesn't follow the convention `tablename_id`, you can specify a
//! custom one to `#[has_many]` and `#[belongs_to]` by adding a `foreign_key` argument to the
//! attribute like so `#[has_many, foreign_key="mykey"]`.
//!
//! `#[has_many]` actually has no behavior on its own. It only enables joining between the two
//! tables. If you are only writing `children.inner_join(parents)` or
//! `Child::belonging_to(&parents)`, you only need to define the `#[belongs_to]` side.

View File

@ -36,10 +36,8 @@ pub trait Connection: SimpleConnection + Sized + Send {
/// a transaction is already occurring, savepoints will be used to emulate a nested
/// transaction.
///
/// If the function returns an `Ok`, that value will be returned. If the
/// function returns an `Err`,
/// [`TransactionError::UserReturnedError`](../result/enum.TransactionError.html#variant.UserReturnedError)
/// will be returned wrapping that value.
/// The error returned from the function must implement
/// `From<diesel::result::Error>`.
fn transaction<T, E, F>(&self, f: F) -> Result<T, E> where
F: FnOnce() -> Result<T, E>,
E: From<Error>,

View File

@ -14,6 +14,7 @@ cfg_if! {
let connection = diesel::pg::PgConnection::establish(&connection_url).unwrap();
connection.begin_test_transaction().unwrap();
connection.execute("DROP TABLE IF EXISTS users").unwrap();
connection.execute("DROP TABLE IF EXISTS animals").unwrap();
connection
}
@ -28,6 +29,16 @@ cfg_if! {
)").unwrap();
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").unwrap();
connection.execute("CREATE TABLE animals (
id SERIAL PRIMARY KEY,
species VARCHAR NOT NULL,
legs INTEGER NOT NULL,
name VARCHAR
)").unwrap();
connection.execute("INSERT INTO animals (species, legs, name) VALUES
('dog', 4, 'Jack'),
('spider', 8, null)").unwrap();
connection
}
} else if #[cfg(feature = "sqlite")] {
@ -48,6 +59,16 @@ cfg_if! {
)").unwrap();
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").unwrap();
connection.execute("CREATE TABLE animals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
species VARCHAR NOT NULL,
legs INTEGER NOT NULL,
name VARCHAR
)").unwrap();
connection.execute("INSERT INTO animals (species, legs, name) VALUES
('dog', 4, 'Jack'),
('spider', 8, null)").unwrap();
connection
}
} else if #[cfg(feature = "mysql")] {
@ -58,6 +79,7 @@ cfg_if! {
let connection_url = database_url_from_env("MYSQL_UNIT_TEST_DATABASE_URL");
let connection = diesel::mysql::MysqlConnection::establish(&connection_url).unwrap();
connection.execute("DROP TABLE IF EXISTS users").unwrap();
connection.execute("DROP TABLE IF EXISTS animals").unwrap();
connection
}
@ -71,8 +93,18 @@ cfg_if! {
name TEXT NOT NULL
) CHARACTER SET utf8mb4").unwrap();
connection.execute("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").unwrap();
connection.begin_test_transaction().unwrap();
connection.execute("CREATE TABLE animals (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
species TEXT NOT NULL,
legs INTEGER NOT NULL,
name TEXT
) CHARACTER SET utf8mb4").unwrap();
connection.execute("INSERT INTO animals (species, legs, name) VALUES
('dog', 4, 'Jack'),
('spider', 8, null)").unwrap();
connection.begin_test_transaction().unwrap();
connection
}
} else {
@ -111,3 +143,39 @@ impl_Insertable! {
name: String,
}
}
table! {
animals {
id -> Integer,
species -> VarChar,
legs -> Integer,
name -> Nullable<VarChar>,
}
}
#[derive(Clone)]
#[allow(dead_code)]
struct NewAnimal {
species: String,
legs: i32,
name: Option<String>,
}
impl NewAnimal {
pub fn new(species: &str, legs: i32, name: Option<&str>) -> Self {
NewAnimal {
species: species.into(),
legs: legs,
name: name.map(|n| n.into()),
}
}
}
impl_Insertable! {
(animals)
struct NewAnimal {
species: String,
legs: i32,
name: Option<String>,
}
}

View File

@ -9,6 +9,27 @@ use types::BigInt;
/// As with most bare functions, this is not exported by default. You can import
/// it specifically as `diesel::expression::count`, or glob import
/// `diesel::expression::dsl::*`
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # include!("src/doctest_setup.rs");
/// # use diesel::expression::dsl::*;
/// #
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
/// #
/// # fn main() {
/// # use self::animals::dsl::*;
/// # let connection = establish_connection();
/// assert_eq!(Ok(1), animals.select(count(name)).first(&connection));
/// # }
/// ```
pub fn count<T: Expression>(t: T) -> Count<T> {
Count {
target: t,
@ -25,7 +46,7 @@ pub fn count<T: Expression>(t: T) -> Count<T> {
/// it specifically as `diesel::expression::count_star`, or glob import
/// `diesel::expression::dsl::*`
///
/// # Example
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate diesel;

View File

@ -56,8 +56,53 @@ macro_rules! fold_function {
fold_function!(sum, Sum, "SUM",
"Represents a SQL `SUM` function. This function can only take types which are
Foldable.");
Foldable.
# Examples
```rust
# #[macro_use] extern crate diesel;
# include!(\"src/doctest_setup.rs\");
# use diesel::expression::dsl::*;
#
# table! {
# users {
# id -> Integer,
# name -> VarChar,
# }
# }
#
# fn main() {
# use self::animals::dsl::*;
# let connection = establish_connection();
assert_eq!(Ok(12i64), animals.select(sum(legs)).first(&connection));
# }
");
fold_function!(avg, Avg, "AVG",
"Represents a SQL `AVG` function. This function can only take types which are
Foldable.");
Foldable.
# Examples
```rust
# #[macro_use] extern crate diesel;
# include!(\"src/doctest_setup.rs\");
# use diesel::expression::dsl::*;
#
# table! {
# users {
# id -> Integer,
# name -> VarChar,
# }
# }
#
# fn main() {
# use self::animals::dsl::*;
# let connection = establish_connection();
// assert_eq!(Ok(6f64), animals.select(avg(legs)).first(&connection));
// TODO: There doesn't currently seem to be a way to use avg with integers, since
// they return a `Numeric` which doesn't have a corresponding Rust type.
# }
```
");

View File

@ -55,8 +55,50 @@ macro_rules! ord_function {
ord_function!(max, Max, "MAX",
"Represents a SQL `MAX` function. This function can only take types which are
ordered.");
ordered.
# Examples
```rust
# #[macro_use] extern crate diesel;
# include!(\"src/doctest_setup.rs\");
# use diesel::expression::dsl::*;
#
# table! {
# users {
# id -> Integer,
# name -> VarChar,
# }
# }
#
# fn main() {
# use self::animals::dsl::*;
# let connection = establish_connection();
assert_eq!(Ok(Some(8)), animals.select(max(legs)).first(&connection));
# }
");
ord_function!(min, Min, "MIN",
"Represents a SQL `MIN` function. This function can only take types which are
ordered.");
ordered.
# Examples
```rust
# #[macro_use] extern crate diesel;
# include!(\"src/doctest_setup.rs\");
# use diesel::expression::dsl::*;
#
# table! {
# users {
# id -> Integer,
# name -> VarChar,
# }
# }
#
# fn main() {
# use self::animals::dsl::*;
# let connection = establish_connection();
assert_eq!(Ok(Some(4)), animals.select(min(legs)).first(&connection));
# }
");

View File

@ -39,7 +39,28 @@ operator_allowed!(now, Add, add);
operator_allowed!(now, Sub, sub);
sql_function!(date, date_t, (x: Timestamp) -> Date,
"Represents the SQL `DATE` function. The argument should be a Timestamp
expression, and the return value will be an expression of type Date");
expression, and the return value will be an expression of type Date.
# Examples
```ignore
# #[macro_use] extern crate diesel;
# extern crate chrono;
# include!(\"src/doctest_setup.rs\");
# use diesel::expression::dsl::*;
#
# table! {
# users {
# id -> Integer,
# name -> VarChar,
# }
# }
#
# fn main() {
# let connection = establish_connection();
let today: chrono::NaiveDate = diesel::select(date(now)).first(&connection).unwrap();
# }
");
#[cfg(feature="postgres")]
use expression::AsExpression;

View File

@ -10,6 +10,10 @@ use types::HasSqlType;
/// Available for when you truly cannot represent something using the expression
/// DSL. You will need to provide the type of the expression, in addition to the
/// SQL. The compiler will be unable to verify the correctness of this type.
///
/// To get a SQL literal, use the [`sql()`] function.
///
/// [`sql()`]: fn.sql.html
pub struct SqlLiteral<ST> {
sql: String,
_marker: PhantomData<ST>,
@ -60,6 +64,54 @@ impl<QS, ST> AppearsOnTable<QS> for SqlLiteral<ST> {
impl<ST> NonAggregate for SqlLiteral<ST> {
}
/// Use literal SQL in the query builder
///
/// Available for when you truly cannot represent something using the expression
/// DSL. You will need to provide the SQL type of the expression, in addition to
/// the SQL.
///
/// # Safety
///
/// The compiler will be unable to verify the correctness of the annotated type.
/// If you give the wrong type, it'll either crash at runtime when deserializing
/// the query result or produce invalid values.
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # #[macro_use] extern crate diesel_codegen;
/// use diesel::expression::sql;
/// use diesel::types::{Bool, Integer, Text};
/// # include!("src/doctest_setup.rs");
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
///
/// #[derive(PartialEq, Debug, Queryable)]
/// struct User {
/// id: i32,
/// name: String,
/// }
///
/// # fn main() {
/// # let connection = establish_connection();
/// #
/// let setup = sql::<Bool>("INSERT INTO users(name) VALUES('Ruby')");
/// setup.execute(&connection).expect("Can't insert in users");
///
/// let query = sql::<(Integer, Text)>("SELECT id, name FROM users WHERE name='Ruby';");
/// let users = query.load::<User>(&connection).expect("Can't query users");
/// assert_eq!(users, vec![User{id: 3, name: "Ruby".to_owned()}]);
///
/// let query = users::table.filter(sql::<Bool>("name='Ruby'")); // Same query as above
/// let users = query.load::<User>(&connection).expect("Can't query users");
/// assert_eq!(users, vec![User{id: 3, name: "Ruby".to_owned()}]);
/// # }
/// ```
pub fn sql<ST>(sql: &str) -> SqlLiteral<ST> {
SqlLiteral::new(sql.into())
}

View File

@ -72,8 +72,41 @@ macro_rules! infer_table_from_schema {
///
/// This macro can only be used in combination with the `diesel_codegen` or
/// `diesel_codegen_syntex` crates. It will not work on its own.
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # #[macro_use] extern crate diesel_codegen;
/// # include!("src/doctest_setup.rs");
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
/// #
/// # #[cfg(feature = "postgres")]
/// # embed_migrations!("../migrations/postgresql");
/// # #[cfg(all(feature = "mysql", not(feature = "postgres")))]
/// # embed_migrations!("../migrations/mysql");
/// # #[cfg(all(feature = "sqlite", not(any(feature = "postgres", feature = "mysql"))))]
/// embed_migrations!("../migrations/sqlite");
///
/// fn main() {
/// let connection = establish_connection();
///
/// // This will run the necessary migrations.
/// embedded_migrations::run(&connection);
///
/// // By default the output is thrown out. If you want to redirect it to stdout, you
/// // should call embedded_migrations::run_with_output.
/// embedded_migrations::run_with_output(&connection, &mut std::io::stdout());
/// }
/// ```
macro_rules! embed_migrations {
() => {
#[allow(dead_code)]
mod embedded_migrations {
#[derive(EmbedMigrations)]
struct _Dummy;
@ -81,6 +114,7 @@ macro_rules! embed_migrations {
};
($migrations_path: expr) => {
#[allow(dead_code)]
mod embedded_migrations {
#[derive(EmbedMigrations)]
#[embed_migrations_options(migrations_path=$migrations_path)]

View File

@ -12,6 +12,35 @@ use query_source::QuerySource;
/// columns.
///
/// This is automatically implemented for the various query builder types.
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # include!("src/doctest_setup.rs");
/// #
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
/// #
/// # fn main() {
/// use self::users::dsl::{users, id, name};
///
/// let connection = establish_connection();
/// # connection.execute("DELETE FROM users").unwrap();
/// connection.execute("INSERT INTO users (name) VALUES ('Saul'), ('Steve'), ('Stan')").unwrap();
/// // load all users' names, ordered by their name descending
/// let ordered_names: Vec<String> = users.select(name).order(name.desc()).load(&connection).unwrap();
/// assert_eq!(vec![String::from("Steve"), String::from("Stan"), String::from("Saul")], ordered_names);
///
/// connection.execute("INSERT INTO users (name) VALUES ('Stan')").unwrap();
/// let ordered_name_id_pairs = users.select((name, id)).order((name.asc(), id.desc())).load(&connection).unwrap();
/// assert_eq!(vec![(String::from("Saul"), 3), (String::from("Stan"), 6), (String::from("Stan"), 5), (String::from("Steve"), 4)], ordered_name_id_pairs);
/// # }
/// ```
pub trait OrderDsl<Expr: Expression>: AsQuery {
type Output: AsQuery<SqlType=Self::SqlType>;

View File

@ -85,7 +85,7 @@ impl<Left, Right, T> SelectableExpression<Join<Left, Right, LeftOuter>>
/// Indicates that two tables can be used together in a JOIN clause.
/// Implementations of this trait will be generated for you automatically by
/// the [association annotations](FIXME: Add link) from codegen.
/// the [association annotations](../associations/index.html) from codegen.
pub trait JoinTo<T: Table, JoinType>: Table {
#[doc(hidden)]
type JoinClause;

View File

@ -30,20 +30,40 @@ macro_rules! numeric_type {
type Output = super::$tpe;
}
impl Add for super::Nullable<super::$tpe> {
type Rhs = super::Nullable<super::$tpe>;
type Output = super::Nullable<super::$tpe>;
}
impl Sub for super::$tpe {
type Rhs = super::$tpe;
type Output = super::$tpe;
}
impl Sub for super::Nullable<super::$tpe> {
type Rhs = super::Nullable<super::$tpe>;
type Output = super::Nullable<super::$tpe>;
}
impl Mul for super::$tpe {
type Rhs = super::$tpe;
type Output = super::$tpe;
}
impl Mul for super::Nullable<super::$tpe> {
type Rhs = super::Nullable<super::$tpe>;
type Output = super::Nullable<super::$tpe>;
}
impl Div for super::$tpe {
type Rhs = super::$tpe;
type Output = super::$tpe;
}
impl Div for super::Nullable<super::$tpe> {
type Rhs = super::Nullable<super::$tpe>;
type Output = super::Nullable<super::$tpe>;
}
)*
}
}

View File

@ -17,9 +17,9 @@ name = "diesel"
chrono = "0.3"
clap = "2.20.3"
diesel = { version = "0.12.0", default-features = false }
dotenv = "0.8.0"
dotenv = ">=0.8, <0.10"
diesel_infer_schema = "0.12.0"
clippy = { optional = true, version = "=0.0.121" }
clippy = { optional = true, version = "=0.0.125" }
[dev-dependencies]
tempdir = "0.3.4"

View File

@ -13,10 +13,10 @@ keywords = ["orm", "database", "postgres", "sql", "codegen"]
[dependencies]
syn = { version = "0.11.4", features = ["aster"] }
quote = "0.3.12"
dotenv = { version = "0.8.0", optional = true }
dotenv = { version = ">=0.8, <0.10", optional = true }
diesel = { version = "0.12.0", default-features = false }
diesel_infer_schema = { version = "0.12.0", default-features = false, optional = true }
clippy = { optional = true, version = "=0.0.121" }
clippy = { optional = true, version = "=0.0.125" }
[dev-dependencies]
tempdir = "0.3.4"

View File

@ -13,10 +13,10 @@ keywords = ["orm", "database", "postgres", "postgresql", "sql"]
diesel = { version = "0.12.0", default-features = false }
syn = "0.11.4"
quote = "0.3.1"
clippy = { optional = true, version = "=0.0.121" }
clippy = { optional = true, version = "=0.0.125" }
[dev-dependencies]
dotenv = "0.8.0"
dotenv = ">=0.8, <0.10"
[features]
default = []

View File

@ -7,17 +7,17 @@ build = "build.rs"
[build-dependencies]
diesel = { path = "../diesel", default-features = false }
dotenv = "0.8.0"
dotenv = ">=0.8, <0.10"
[dependencies]
assert_matches = "1.0.1"
chrono = { version = "0.3" }
diesel = { path = "../diesel", default-features = false, features = ["quickcheck", "chrono", "uuid", "serde_json"] }
diesel_codegen = { path = "../diesel_codegen" }
dotenv = "0.8.0"
dotenv = ">=0.8, <0.10"
quickcheck = { version = "0.3.1", features = ["unstable"] }
uuid = { version = ">=0.2.0, <0.5.0" }
serde_json = "0.9"
serde_json = { version=">=0.9, <2.0" }
[features]
default = []

View File

@ -92,6 +92,77 @@ fn dividing_column() {
assert_eq!(Ok(expected_data), data);
}
#[test]
fn test_adding_nullables() {
use schema::nullable_table::dsl::*;
let connection = connection_with_nullable_table_data();
let expected_data = vec![None, None, Some(2), Some(3), Some(2)];
let data = nullable_table.select(value + Some(1)).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data: Vec<Option<i32>> = vec![None; 5];
let data = nullable_table.select(value + None as Option<i32>).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data = vec![None, None, Some(2), Some(4), Some(2)];
let data = nullable_table.select(value + value).load(&connection);
assert_eq!(Ok(expected_data), data);
}
#[test]
fn test_substracting_nullables() {
use schema::nullable_table::dsl::*;
let connection = connection_with_nullable_table_data();
let expected_data = vec![None, None, Some(0), Some(1), Some(0)];
let data = nullable_table.select(value - Some(1)).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data: Vec<Option<i32>> = vec![None; 5];
let data = nullable_table.select(value - None as Option<i32>).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data = vec![None, None, Some(0), Some(0), Some(0)];
let data = nullable_table.select(value - value).load(&connection);
assert_eq!(Ok(expected_data), data);
}
#[test]
fn test_multiplying_nullables() {
use schema::nullable_table::dsl::*;
let connection = connection_with_nullable_table_data();
let expected_data = vec![None, None, Some(3), Some(6), Some(3)];
let data = nullable_table.select(value * Some(3)).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data: Vec<Option<i32>> = vec![None; 5];
let data = nullable_table.select(value * None as Option<i32>).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data = vec![None, None, Some(1), Some(4), Some(1)];
let data = nullable_table.select(value * value).load(&connection);
assert_eq!(Ok(expected_data), data);
}
#[test]
fn test_dividing_nullables() {
use schema::nullable_table::dsl::*;
let connection = connection_with_nullable_table_data();
let expected_data = vec![None, None, Some(0), Some(1), Some(0)];
let data = nullable_table.select(value / Some(2)).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data: Vec<Option<i32>> = vec![None; 5];
let data = nullable_table.select(value / None as Option<i32>).load(&connection);
assert_eq!(Ok(expected_data), data);
let expected_data = vec![None, None, Some(1), Some(1), Some(1)];
let data = nullable_table.select(value / value).load(&connection);
assert_eq!(Ok(expected_data), data);
}
#[test]
fn mix_and_match_all_numeric_ops() {
use schema::users::dsl::*;

View File

@ -148,6 +148,15 @@ impl FkTest {
}
}
numeric_expr!(nullable_table::value);
#[derive(Queryable, Insertable)]
#[table_name="nullable_table"]
pub struct NullableColumn {
id: i32,
value: Option<i32>,
}
#[cfg(feature = "postgres")]
pub type TestConnection = ::diesel::pg::PgConnection;
#[cfg(feature = "sqlite")]
@ -203,6 +212,22 @@ pub fn connection_with_sean_and_tess_in_users_table() -> TestConnection {
connection
}
pub fn connection_with_nullable_table_data() -> TestConnection {
let connection = connection();
let test_data = vec![
NullableColumn { id: 1, value: None },
NullableColumn { id: 2, value: None },
NullableColumn { id: 3, value: Some(1) },
NullableColumn { id: 4, value: Some(2) },
NullableColumn { id: 5, value: Some(1) },
];
insert(&test_data).into(nullable_table::table)
.execute(&connection).unwrap();
connection
}
fn ensure_primary_key_seq_greater_than(x: i64, connection: &TestConnection) {
if cfg!(feature = "postgres") {
for _ in 0..x {

View File

@ -0,0 +1 @@
DROP TABLE nullable_table;

View File

@ -0,0 +1,4 @@
CREATE TABLE nullable_table (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
value INTEGER
) CHARACTER SET utf8mb4;

View File

@ -0,0 +1 @@
DROP TABLE nullable_table;

View File

@ -0,0 +1,4 @@
CREATE TABLE nullable_table (
id SERIAL PRIMARY KEY,
value INTEGER
);

View File

@ -0,0 +1 @@
DROP TABLE nullable_table;

View File

@ -0,0 +1,4 @@
CREATE TABLE nullable_table (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
value INTEGER
);