Skip to content

Commit

Permalink
Use BigInt for integer primary key in sqlite for diesel-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
stormshield-kg committed Feb 22, 2024
1 parent a6b04d2 commit c3ab372
Show file tree
Hide file tree
Showing 28 changed files with 398 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ jobs:
env:
BACKEND: ${{ matrix.backend }}
run: |
(cd examples/${{ matrix.backend }} && rustup run ${{ matrix.rust }} bash test_all)
(cd examples/${{ matrix.backend }} && rustup run ${{ matrix.rust }} bash test_all)
- name: Test migrations-internals
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion diesel_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pq-sys = { version = ">=0.4, <0.6.0", optional = true }
diffy = "0.3.0"
regex = "1.0.6"
serde_regex = "1.1"
diesel_table_macro_syntax = {version = "0.1", path = "../diesel_table_macro_syntax"}
diesel_table_macro_syntax = { version = "0.1", path = "../diesel_table_macro_syntax" }
syn = { version = "2", features = ["visit"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.10", features = ["env-filter"] }
Expand Down
22 changes: 22 additions & 0 deletions diesel_cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ pub fn build_cli() -> Command {
.num_args(0..=1)
.require_equals(true),
)
.arg(
Arg::new("sqlite-integer-primary-key-is-bigint")
.long("sqlite-integer-primary-key-is-bigint")
.requires("SCHEMA_RS")
.action(ArgAction::SetTrue)
.help(
"For SQLite 3.37 and above, detect `INTEGER PRIMARY KEY` columns as `BigInt`, \
when the table isn't declared with `WITHOUT ROWID`.\n\
See https://www.sqlite.org/lang_createtable.html#rowid for more information.\n\
Only used with the `--diff-schema` argument."
),
)
.arg(
Arg::new("table-name")
.index(2)
Expand Down Expand Up @@ -289,6 +301,16 @@ pub fn build_cli() -> Command {
.action(clap::ArgAction::Append)
.number_of_values(1)
.help("A list of derives to implement for every automatically generated SqlType in the schema, separated by commas."),
)
.arg(
Arg::new("sqlite-integer-primary-key-is-bigint")
.long("sqlite-integer-primary-key-is-bigint")
.action(ArgAction::SetTrue)
.help(
"For SQLite 3.37 and above, detect `INTEGER PRIMARY KEY` columns as `BigInt`, \
when the table isn't declared with `WITHOUT ROWID`.\n\
See https://www.sqlite.org/lang_createtable.html#rowid for more information."
),
);

let config_arg = Arg::new("CONFIG_FILE")
Expand Down
2 changes: 2 additions & 0 deletions diesel_cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub struct PrintSchema {
pub generate_missing_sql_type_definitions: Option<bool>,
#[serde(default)]
pub custom_type_derives: Option<Vec<String>>,
#[serde(default)]
pub sqlite_integer_primary_key_is_bigint: Option<bool>,
}

impl PrintSchema {
Expand Down
19 changes: 11 additions & 8 deletions diesel_cli/src/infer_schema_internals/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use diesel::result::Error::NotFound;
use super::data_structures::*;
use super::table_data::*;

use crate::config::Filtering;
use crate::config::{Filtering, PrintSchema};

use crate::database::InferConnection;
use crate::print_schema::{ColumnSorting, DocConfig};
Expand Down Expand Up @@ -185,10 +185,14 @@ fn get_column_information(
fn determine_column_type(
attr: &ColumnInformation,
conn: &mut InferConnection,
#[allow(unused_variables)] table: &TableName,
#[allow(unused_variables)] config: &PrintSchema,
) -> Result<ColumnType, crate::errors::Error> {
match *conn {
#[cfg(feature = "sqlite")]
InferConnection::Sqlite(_) => super::sqlite::determine_column_type(attr),
InferConnection::Sqlite(ref mut conn) => {
super::sqlite::determine_column_type(conn, attr, table, config)
}
#[cfg(feature = "postgres")]
InferConnection::Pg(ref mut conn) => {
use crate::infer_schema_internals::information_schema::DefaultSchema;
Expand Down Expand Up @@ -259,11 +263,10 @@ pub fn load_foreign_key_constraints(
pub fn load_table_data(
connection: &mut InferConnection,
name: TableName,
column_sorting: &ColumnSorting,
with_docs: DocConfig,
config: &PrintSchema,
) -> Result<TableData, crate::errors::Error> {
// No point in loading table comments if they are not going to be displayed
let table_comment = match with_docs {
let table_comment = match config.with_docs {
DocConfig::NoDocComments => None,
DocConfig::OnlyDatabaseComments
| DocConfig::DatabaseCommentsFallbackToAutoGeneratedDocComment => {
Expand All @@ -275,12 +278,12 @@ pub fn load_table_data(
let primary_key = primary_key
.iter()
.map(|k| rust_name_for_sql_name(k))
.collect();
.collect::<Vec<_>>();

let column_data = get_column_information(connection, &name, column_sorting)?
let column_data = get_column_information(connection, &name, &config.column_sorting)?
.into_iter()
.map(|c| {
let ty = determine_column_type(&c, connection)?;
let ty = determine_column_type(&c, connection, &name, config)?;

let ColumnInformation {
column_name,
Expand Down
153 changes: 150 additions & 3 deletions diesel_cli/src/infer_schema_internals/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use diesel::*;

use super::data_structures::*;
use super::table_data::TableName;
use crate::config::PrintSchema;
use crate::print_schema::ColumnSorting;

table! {
Expand Down Expand Up @@ -204,6 +205,65 @@ impl QueryableByName<Sqlite> for PrimaryKeyInformation {
}
}

struct WithoutRowIdInformation {
name: String,
without_row_id: bool,
}

impl QueryableByName<Sqlite> for WithoutRowIdInformation {
fn build<'a>(row: &impl NamedRow<'a, Sqlite>) -> deserialize::Result<Self> {
Ok(Self {
name: NamedRow::get::<Text, String>(row, "name")?,
without_row_id: NamedRow::get::<Bool, bool>(row, "wr")?,
})
}
}

pub fn column_is_row_id(
conn: &mut SqliteConnection,
table: &TableName,
column_name: &str,
type_name: &str,
) -> Result<bool, crate::errors::Error> {
let sqlite_version = get_sqlite_version(conn)?;
if sqlite_version < SqliteVersion::new(3, 37, 0) {
return Err(crate::errors::Error::UnsupportedFeature(
"Parameter `sqlite_integer_primary_key_is_bigint` needs SQLite 3.37 or above".into(),
));
}

if type_name != "integer" {
return Ok(false);
}

let table_xinfo_query = format!("PRAGMA TABLE_XINFO('{}')", &table.sql_name);
let table_xinfo_results = sql_query(table_xinfo_query).load::<PrimaryKeyInformation>(conn)?;

let primary_keys = table_xinfo_results
.iter()
.filter(|pk_info| pk_info.primary_key)
.collect::<Vec<_>>();

if primary_keys.len() != 1 {
return Ok(false);
}

if primary_keys[0].name != column_name {
return Ok(false);
}

let table_list_query = format!("PRAGMA TABLE_LIST('{}')", &table.sql_name);
let table_list_results = sql_query(table_list_query).load::<WithoutRowIdInformation>(conn)?;

let res = table_list_results
.iter()
.find(|wr_info| wr_info.name == table.sql_name)
.map(|wr_info| !wr_info.without_row_id)
.unwrap_or_default();

Ok(res)
}

#[derive(Queryable)]
struct ForeignKeyListRow {
_id: i32,
Expand Down Expand Up @@ -260,8 +320,13 @@ pub fn get_primary_keys(
Ok(collected)
}

#[tracing::instrument]
pub fn determine_column_type(attr: &ColumnInformation) -> Result<ColumnType, crate::errors::Error> {
#[tracing::instrument(skip(conn))]
pub fn determine_column_type(
conn: &mut SqliteConnection,
attr: &ColumnInformation,
table: &TableName,
config: &PrintSchema,
) -> Result<ColumnType, crate::errors::Error> {
let mut type_name = attr.type_name.to_lowercase();
if type_name == "generated always" {
type_name.clear();
Expand All @@ -274,7 +339,17 @@ pub fn determine_column_type(attr: &ColumnInformation) -> Result<ColumnType, cra
} else if is_bigint(&type_name) {
String::from("BigInt")
} else if type_name.contains("int") {
String::from("Integer")
let sqlite_integer_primary_key_is_bigint = config
.sqlite_integer_primary_key_is_bigint
.unwrap_or_default();

if sqlite_integer_primary_key_is_bigint
&& column_is_row_id(conn, table, &attr.column_name, &type_name)?
{
String::from("BigInt")
} else {
String::from("Integer")
}
} else if is_text(&type_name) {
String::from("Text")
} else if is_binary(&type_name) {
Expand Down Expand Up @@ -475,3 +550,75 @@ fn all_rowid_aliases_used_empty_result() {
assert!(res.is_ok());
assert!(res.unwrap().is_empty());
}

#[test]
fn integer_primary_key_sqlite_3_37() {
let mut conn = SqliteConnection::establish(":memory:").unwrap();

let sqlite_version = get_sqlite_version(&mut conn).unwrap();
if sqlite_version < SqliteVersion::new(3, 37, 0) {
return;
}

diesel::sql_query("CREATE TABLE table_1 (id INTEGER PRIMARY KEY)")
.execute(&mut conn)
.unwrap();

diesel::sql_query("CREATE TABLE table_2 (id INTEGER, PRIMARY KEY(id))")
.execute(&mut conn)
.unwrap();

diesel::sql_query("CREATE TABLE table_3 (id INTEGER)")
.execute(&mut conn)
.unwrap();

diesel::sql_query("CREATE TABLE table_4 (id1 INTEGER, id2 INTEGER, PRIMARY KEY(id1, id2))")
.execute(&mut conn)
.unwrap();

diesel::sql_query("CREATE TABLE table_5 (id INT PRIMARY KEY)")
.execute(&mut conn)
.unwrap();

diesel::sql_query("CREATE TABLE table_6 (id INTEGER PRIMARY KEY) WITHOUT ROWID")
.execute(&mut conn)
.unwrap();

let tables = [
TableName::from_name("table_1"),
TableName::from_name("table_2"),
TableName::from_name("table_3"),
TableName::from_name("table_4"),
TableName::from_name("table_5"),
TableName::from_name("table_6"),
];

let column_infos = tables
.iter()
.map(|table| get_table_data(&mut conn, table, &Default::default()).unwrap()[0].clone())
.collect::<Vec<_>>();

for (table, column_info) in std::iter::zip(&tables, &column_infos) {
let column_type = determine_column_type(&mut conn, column_info, table, &Default::default());
assert_eq!(column_type.unwrap().sql_name, "Integer");
}

let config = PrintSchema {
sqlite_integer_primary_key_is_bigint: Some(true),
..Default::default()
};

let column_types = std::iter::zip(&tables, &column_infos)
.map(|(table, column_info)| {
determine_column_type(&mut conn, column_info, table, &config)
.unwrap()
.sql_name
})
.collect::<Vec<_>>();

let expected_column_types = vec![
"BigInt", "BigInt", "Integer", "Integer", "Integer", "Integer",
];

assert_eq!(column_types, expected_column_types);
}
4 changes: 4 additions & 0 deletions diesel_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ fn run_infer_schema(matches: &ArgMatches) -> Result<(), crate::errors::Error> {
config.custom_type_derives = Some(derives);
}

if matches.get_flag("sqlite-integer-primary-key-is-bigint") {
config.sqlite_integer_primary_key_is_bigint = Some(true);
}

run_print_schema(&mut conn, &config, &mut stdout())?;
Ok(())
}
Expand Down
Loading

0 comments on commit c3ab372

Please sign in to comment.