Repository: diesel-rs/diesel (language: rust)

Issue:
INTEGER PRIMARY KEY in SQLite
Diesel deduces the a column's type as `BigInt` if it's declared as `BIGINT` in the database. This is generally fine in SQLite because `INTEGER` and `BIGINT` are synonyms, but `INTEGER PRIMARY KEY` is treated differently and is always 64-bit according to https://sqlite.org/autoinc.html. Moreso, `BIGINT PRIMARY KEY AUTOINCREMENT` is not accepted.

It might be a good idea to detect these columns and use `BigInt` for them. Note that the SQLite documentation says that `INTEGER PRIMARY KEY` is special only for tables that aren't declared with `WITHOUT ROWID`, but `BIGINT PRIMARY KEY` is still not allowed even when using that.


Discussion / hints:
Is this really an issue ? I just tried and I was able to insert 2^256 in an integer field. SQLite does not really care about types. It would be confusing for users to deserialize what they think is an integer to an i64 though as I'm sure nobody knows about this bug. If you need to retrieve a I Bigint from SQLite, you can use the table macro and say it's a bigint 
It's not an issue for `SQLite`, but `diesel` deduces the type as `i32` IIRC.

> If you need to retrieve a I Bigint from SQLite, you can use the table macro and say it's a bigint

Yeah, that works.
I'm a bit torn on what to do here. It's really unfortunate that SQLite requires the type name to be exactly `INTEGER` in this case. As @Eijebong mentioned, our inference on SQLite is really more just trying to deduce intent, as types are just suggestions more than anything in SQLite. Ultimately I'd prefer not to special case this, as the use cases for SQLite trend towards not needing 64 bit PKs, and you can override manually in the cases where it matters. Ultimately though I think keeping our inference rules simple and consistent here takes priority.
Is there a reason i32 was picked instead of i64? I can store an i32 in a i64 but not the other way around, so using a i64 seems like the obviously better choice.

Are there any docs I can read on how to work around this? I'm currently stuck with a number that I need to store but can't.
I think you can use `diesel print-schema` and edit the resulting file to use your own types.
@kpcyrd To [quote](https://github.com/diesel-rs/diesel/issues/852#issuecomment-315755841) Sean from the replay literally above yours:

> Ultimately I'd prefer not to special case this, as the use cases for SQLite trend towards not needing 64 bit PKs, and you can override manually in the cases where it matters. Ultimately though I think keeping our inference rules simple and consistent here takes priority.
My column is not a primary key, should I raise a separate issue for this?

For anybody with the same issue, I'm currently editing my `src/schema.rs` manually like this:
```
-        balance -> Nullable<Integer>,
+        balance -> Nullable<BigInt>,
```
Since this file is auto-generated it is overwritten every time `diesel migration run` and `diesel migration redo` is used.
> Since this file is auto-generated it is overwritten every time diesel migration run and diesel migration redo is used.

I didn't realize that. How does `diesel migration run` which is the schema file? Are you sure you don't have `diesel print-schema` somewhere?
```
% cat diesel.toml 
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/schema.rs"
```
I think it's correct that the file is rewritten to pick up possible schema changes, the problem is that I need to edit this file afterwards. Allowing me to overwrite the type for certain fields with a config file might work as a solution.
Ah, okay. My solution was to not run `print-schema` and maintain it manually.
@kpcyrd If you column is not a primary key you can just use `BigInt` as column type on sql side then `diesel print-schema` will pick things automatically.

About changing the auto generated schema: [This](http://diesel.rs/guides/configuring-diesel-cli/) guide is covering options to modify the schema after generation.
@weiznich I have a primary key and want it to be i64. Your proposed [guide](http://diesel.rs/guides/configuring-diesel-cli/) mentions the `patch_file` field. This works, but only until I change the database schema. Then the patch file does not match with the newly generated `schema.rs` anymore and the following error occurs:

```
Failed to apply schema patch. stdout: patching file src/schema.rs
```

So after changing the database schema I would still have to manually change the `schema.rs` and additionally rebuild the `schema.patch` file.
Ok, now I understand. First, deactivate the auto-generation of the `schema.rs` in  `diesel-toml`:

```
[print_schema]
# file = "src/schema.rs"
```

Then use the command `diesel print_schema` and edit the `schema.rs` manually.

I can live with that, but I still do not understand how `patch_file` should generally work after a schema change.
@tobx `patch_file` just applies the given file via patch to the generated schema file, so there is no magic involved there. This means applying the patch is only possible as long as you schema didn't change in all places at once.  (The context for the changed lines needs not to be changed). That does entirely depend on your changes to the database schema, so it can work or it cannot work.
@weiznich `patch_file` is not working for me in Git shell / msys:
```
$ diesel print-schema
Failed to apply schema patch. stdout: patching file 'C:\Users\me\AppData\Local\Temp\.tmp0VJQNn'
Hunk #1 FAILED at 43.
Hunk #2 FAILED at 67.
2 out of 2 hunks FAILED -- saving rejects to file 'C:\Users\me\AppData\Local\Temp\.tmp0VJQNn.rej'
 stderr:
```
How can I make it work on Windows?

I followed the guide and did `git diff -U6 > src/schema.patch` and I have 
```toml
[print_schema]
file = "src/schema.rs"
patch_file = "src/schema.patch"
```
I want to propose reopening this.  While it is true that most applications using sqlite won't ever have 4 billion rows in a table at a time, sqlite's autoincrement algorithm can and will generate integers above the i32 limit.  This means that if you end up with around 2 billion inserts to a table, you'll end up over the i32 limit and something will happen.  It would be nice to at least clarify what that something is.  This isn't rows in the table, just inserts.

You can hit this in a month with a  mere 1000 updates per second, so that's kind of rendering schema inference unsuitable for any application with 1000 updates a second.  That seems like a pretty low limit.  You could hit that just using sqlite to store metrics data or logs or something with a  retention policy in a pretty reasonable time frame.  For more reasonable sqlite volumes, you can hit it in a year or so, say 100 or 200 updates a second.  Your database needn't even go over a few megabytes to make that happen.

I understand why you wouldn't want to make the schema inference special, but it's already bad enough to maintain two places on every schema change, and for production applications you appear to literally have no choice but to do so if you don't want to hit the above problem.  If there's also an underlying ticking time bomb that's going to fail silently, I strongly suggest that fixing said problem would be worth the special case.  This needs to at least fail loudly, otherwise you just get spurious failures that you'd have to spend an afternoon tracking down.
@camlorn As it is simple to change the corresponding field type in the autogenerated schema later I do not see any reason to change the behavior here. Additionally the CLI tool provides multiple ways to automate such changes for future schema changes, so I really do not see why we should break a lot of already existing code to fix a "special" case.
Nb. I'm the one who filed this issue.

I've come to think that this is actually a bug in `diesel`: if the SQLite docs say that `INTEGER PRIMARY KEY` is `Integer64`, that's what `diesel print-schema` should output and `Integer32` is incorrect. It may seem like a special case, but it's a special case in the SQLite specification, not in `diesel`.

As for the use cases for SQLite and whether they need 64-bit surrogate keys or not, I think that's up to the application developers to decide, and not the ORM's choice. Consider that SQLite is actually used in embedded systems. 

On the other hand, manually updating the generated schema on changes will usually take less time than installing the `diesel`  CLI from scratch. If the CLI doesn't (or can't) produce the right types, it's worth being explicit. You still have to update the database and the model, so one more change for the schema isn't that much of a hassle. So don't blindly trust the ORM generated code, but use it as a starting point and then maintain it like you maintain your models.

> I really do not see why we should break a lot of already existing code to fix a "special" case.

It could be done for a 2.0 release, whenever that happens.
> I really do not see why we should break a lot of already existing code to fix a "special" case.

As @camlorn said, a silent failure in a production system would be a good reason to fix this case. I think not everyone should be forced to understand such detail of the ORM just to be sure to run a production system that will not randomly break after a month or a year.

To avoid breaking code maybe for now the system could just print out a big warning for this case?
First of all the type mapping support from diesel for sqlite has been that what we as diesel maintainers have defined by ourself as being reasonable since diesel supports sqlite. That's because in the end types do not really matter for sqlite. From a theoretical point of view we as developer cannot assume that a column defined as `Integer` in sqlite will actually contain numbers, as sqlite happy accepts any value there. So following the reasoning behind this issue that the generated `schema.rs` should be correct the only fully "correct" solution here would be not to generate any schema as we cannot do that without assuming a lot of things. 


So let's assume we only want to "fix" this specific issue here:
We currently generate column types based on a mapping based on the type names used  by postgres/mysql. See [here](https://github.com/diesel-rs/diesel/blob/2ba97080c476b0f342f663bc5e33f5bd0d4982cd/diesel_cli/src/infer_schema_internals/sqlite.rs#L130-L164) for the exact mapping code. If you look a bit more in detail in the surrounding code you will see that we are getting the column type names via `PRAGMA TABLE_INFO('table_name')` from sqlite. 
So let's just do a small experiment: Let's create the following two tables and have a look at the output of `PRAGMA TABLE_INFO`:
```sql
create table test(id integer primary key autoincrement);
create table test2(id integer primary key);
```

Now let's have a look at the corresponding output:
```
sqlite> pragma table_info('test');
cid|name|type|notnull|dflt_value|pk
0|id|integer|0||1

sqlite> pragma table_info('test2');
cid|name|type|notnull|dflt_value|pk
0|id|integer|0||1
```

As you can see both return the same table info. That means we do not have any chance to even infer if a primary key is `autoincrement` or not. The only place where this information is available is in the corresponding `CREATE TABLE` statement contained in the `sqlite_master` table.  Getting the information from there would require parsing those statements. At least I do not plan to implement a SQL parser any time soon, as there are quite a lot things that are more important for me.
 
That's a fair reason. Pre-emptively (since others will probably point them out anyway), you can use some heuristics:

 - look in `sqlite_sequence` for a sequence with the same name as the table
 - look for `primary key autoincrement` in the table's `sqlite_master` entry

Neither of them is perfect (e.g. first one breaks when you rename the table or if it's never had any data), but they would probably cover most of the users. And it only reinforces my point about "declare your column types instead of letting the ORM infer them".

But I agree that both of the solutions above are icky and you're not unreasonable if you don't want to implement something like that.
Just for the sake of completeness:

> * look in sqlite_sequence for a sequence with the same name as the table

As already noted that works only for tables that already have entries. I would say that this assumption is not true for a significant amount of tables that are generated via `diesel migration run` + where schema.rs is generated directly afterwards (by `diesel migration run` for example). Therefore this approach is not acceptable, as it would break on a relatively common use case.

> * look for primary key autoincrement in the table's sqlite_master entry

As written above: that requires parsing sql, because otherwise this will fail as soon as someone has something like the following table:
```sql
create table test3(text defaut 'primary key autoincrement');
```
As already stated: It least I do not want to write or maintain a sql parser for sqlites create table statements.

>  And it only reinforces my point about "declare your column types instead of letting the ORM infer them".

That's definitively a valid strategy. To reiterate this: Diesel explicitly supports this use case, it's at any point possible to write your own `table!` macro calls or edit those generated by diesel-cli.
For anyone else who finds this later and who wants to use schema inference, the solution appears to be as follows:

Create a `sqlite_mapping.rs` containing the following:

```
use diesel::sql_types::*;

type Integer = BigInt;
```

Then, in your print-schema, add `--import-types mycrate::sqlite_mapping::*`.  I'm typing this off memory, sorry if there's typos.

If it's obvious, I missed where it's documented.  There's a couple offhand mentions of `--import-types` in various places but that's all I could find.

I now understand that it breaks code to change this (and why), but I'm not too enthused about the silent breakage.  I almost missed this point myself.  Your autogenerated schema will work fine until it doesn't and until it doesn't is far enough out that it'd be in prod by that point in all likelihood.  At the moment, the only warning you get is that i64 doesn't work.  Maybe this should be called out somewhere in the documentation?

Though beside the point for this issue, "maintain everything yourself" doesn't make Diesel competitive with options in other languages.  Checking is a good idea, but going down that road gives manually written sql migrations, manually written `table!` macros, and a set of structs whose fields have to be kept in order with both of the proceeding.  I understand why these should be decoupled components, but it feels like taking the stance of doing all of this yourself makes it easier only on the developers of Diesel.
> For anyone else who finds this later and who wants to use schema inference, the solution appears to be as follows:

> Create a sqlite_mapping.rs containing the following:

To call that out at least once explicitly: This will change the mapping for all `Integer` columns in your database to `i64`, not only the `PRIMARY KEY AUTOINCREMENT` ones. Depending on your use case this can be fine or not...

> Then, in your print-schema, add --import-types mycrate::sqlite_mapping::*. I'm typing this off memory, sorry if there's typos.

> If it's obvious, I missed where it's documented. There's a couple offhand mentions of --import-types in various places but that's all I could find.

As for the location where options to manipulate the generated schema are documented like this are documented, have a look at [this](diesel.rs/guides/configuring-diesel-cli/) guide at the official web page? Otherwise I'm fine with getting a PR extending that documentation to make this more clear.

> Though beside the point for this issue, "maintain everything yourself" doesn't make Diesel competitive with options in other languages. Checking is a good idea, but going down that road gives manually written sql migrations, manually written table! macros, and a set of structs whose fields have to be kept in order with both of the proceeding. I understand why these should be decoupled components, but it feels like taking the stance of doing all of this yourself makes it easier only on the developers of Diesel.

I mean nobody forces you to use diesel. If you don't like it just use something different or write your own ORM 🤷 . I do not consider it as friendly to call out design decisions because "makes it easier only on the developers of Diesel". I mean we provide this crate in our free time, so maybe recheck your wording.
 That said the guide I've mentioned above lists 2 possibilities how to in cooperate that with automatic schema generation.
I'm not saying that I have a problem with the diesel developers, only that the justification here seems weak and the usability much less than ideal.  Not having the resources and developing something for free are indeed good reasons to not do something, but the justification here seems to be that manually maintaining these is fine, and if it's a resource issue then in general I'd have expected someone to say "that's a resource issue".  Perhaps they did and I missed it.

Not having one authoritative location for configuration in your code is a good way to introduce bugs.  But if there's  wider discussion to be had, that's probably out of scope for this issue in any case, and regardless I found (and documented) a workaround which at least temporarily reduces the pain points for people who need to use Sqlite.  maybe I will revisit this topic in general somewhere else in a month or two after we've finished adopting the Rust stack, when I may perhaps be able to propose concrete action.
Upvote for re-opening this issue - it's clearly a bug in `print-schema`. 

The fact that `print-schema` depends on a single-source, Pg-derived lookup table for type mapping is an implementation detail. The chosen implementation does not scale to the sqlite use case.

Manually maintaining the schema  may be an acceptable work-around and the cost of fixing this bug may exceed the benefit. If so, I'd propose a loud warning from `print-schema` when it encounters an integer in the sqlite context.
@curtisleefulton As outlined above: I do not see this issue as actionable, therefore it remains closed as long as noone can point out a solution that is:
* backward compatible as we will not release a breaking diesel version anytime soon
* addresses all the problems listed above

I'm happy to read your suggestions how to address these point. Otherwise its discouraged to post on old issues to just repeat what already has been said.

I do not consider adding a bolt warning here as a valid solution as I expect that only fraction of our users will ever have more than `i32::MAX` table entries.
Hello, my first post here so first thank you to the Diesel developers and contributors for this great library. I also ran into this issue. Good discussion and I agree it's a tricky one. 

I also worked around it via `diesel.toml`:

```toml
[print_schema]
import_types = ["crate::sqlite_mapping::*"]
file = "src/schema.rs"
```

`sqlite_mapping.rs`:
```rust
// https://github.com/diesel-rs/diesel/issues/852
pub use diesel::sql_types::*;
// This will change the mapping for all Integer columns in your database to i64, not only the PRIMARY KEY AUTOINCREMENT ones.
// Depending on your use case this can be fine or not...
pub type Integer = BigInt;
```

Perhaps it's arguably a bug in SQLIte as it should, instead of,

```sql
CREATE TABLE items (
  id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
)
```

let you write:

```sql
CREATE TABLE items (
  id BIGINT PRIMARY KEY AUTOINCREMENT NOT NULL
)
```

because in the `BIGINT` case SQLite will fail with `AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY`

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d1b50011926a..5677f3d12c92 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8bdfef85ed44..fff8d3863205 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,9 @@
 All user visible changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/), as described
 for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md)
-For any named minimal supported Rust version we guarantee that it is possible to build Diesel with the 
-default features enabled using some set of dependencies. Those set of dependencies is not necessarily 
-an up to date version of the specific dependency. We check this by using the unstable `-Z minimal-version` cargo flag. 
+For any named minimal supported Rust version we guarantee that it is possible to build Diesel with the
+default features enabled using some set of dependencies. Those set of dependencies is not necessarily
+an up to date version of the specific dependency. We check this by using the unstable `-Z minimal-version` cargo flag.
 Increasing the minimal supported Rust version will always be coupled at least with a minor release.
 
 ## Unreleased
@@ -19,6 +19,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 * Support for connection instrumentation. This allows to inspect any query run by your application
 * Logging in diesel-cli
 * Support for libsqlite3-sys 0.28
+* Add `sqlite-integer-primary-key-is-bigint` configuration option, usable with SQLite 3.37 or above, allowing to use `BigInt` for `INTEGER PRIMARY KEY` columns in SQLite for tables without the `WITHOUT ROWID` attribute ([SQLite doc](https://www.sqlite.org/lang_createtable.html#rowid)).
 
 ### Changed
 
@@ -26,7 +27,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 
 ## [2.1.0] 2023-05-26
 
-### Changed 
+### Changed
 
 * The minimal officially supported rustc version is now 1.65.0
 
@@ -48,7 +49,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 
 ## [2.0.4] 2023-04-18
 
-## Fixed 
+## Fixed
 
 * Workaround the missing name resolution in rust-analyzer. This should fix type inference for some diesel queries. (It remains broken for queries containing `.filter()`/`.inner_join()`/`.left_join()`. These require fixes in rust-analyzer itself)
 * Fixed a bug that could lead to inserting null values instead of empty values for custom sqlite types
@@ -59,9 +60,9 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 
 * Support for `libsqlite3-sys` 0.26
 
-## [diesel_derives 2.0.2] 2023-03-13 
+## [diesel_derives 2.0.2] 2023-03-13
 
-## Fixed 
+## Fixed
 
 * Fixing the fallout of a breaking change from `quote` by not using their internal API
 
@@ -130,7 +131,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
   in such a way to support constructing a dynamic value depending on this type.
 
 * Added a `without-deprecated` feature that unconditionally disables deprecated items.
-  Use this feature flag to verify that none of your dependencies is setting 
+  Use this feature flag to verify that none of your dependencies is setting
   the `with-deprecated` flag internally.
 
 * Added support for PostgreSQL's `SIMILAR TO` and `NOT SIMILAR TO`.
@@ -154,7 +155,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 
 * Diesel CLI will now generate SQL type definitions for SQL types that are not supported by diesel out of the box. It's possible to disable this behavior via the `generate_missing_sql_type_definitions` config option.
 
-* Added an option to `#[derive(Insertable)]` that let you insert `NULL` values instead of `DEFAULT` values for `Option<T>` 
+* Added an option to `#[derive(Insertable)]` that let you insert `NULL` values instead of `DEFAULT` values for `Option<T>`
 
 * Added support for all the derive attributes being inside `#[diesel(...)]`
 
@@ -259,12 +260,12 @@ Increasing the minimal supported Rust version will always be coupled at least wi
   card implementations for types implementing `Queryable<ST, DB>` or `QueryableByName<DB>`
   so non generic code does not require any change. For generic code you likely need to
   replace a trait bound on `Queryable<ST, DB>` with a trait bound on `FromSqlRow<ST, DB>`
-  and a bound to `QueryableByName<DB>` with `FromSqlRow<Untyped, DB>`. 
+  and a bound to `QueryableByName<DB>` with `FromSqlRow<Untyped, DB>`.
 
 * CLI flags of `only-tables` and `except-tables` are now interpreted as regular expressions.
   Similarly, `only_tables` and `except_tables` in `diesel.toml` are treated as regular expressions.
 
-* Now you can sort column fields by name with the `column-sorting` option. 
+* Now you can sort column fields by name with the `column-sorting` option.
   It can be set to either `ordinal_position` (default) or `name`.
   This ensures stable sorting even if columns are removed and re-added.
 
@@ -277,25 +278,25 @@ Increasing the minimal supported Rust version will always be coupled at least wi
 
 * `TypeMetadata::MetadataLookup` is now `?Sized`.
 
-* Multiple implementations of `Connection<Backend=Pg>` are now possible 
+* Multiple implementations of `Connection<Backend=Pg>` are now possible
   because of the new `PgMetadataLookup` trait.
 
 * For the `Pg` backend, `TypeMetadata::MetadataLookup` has changed to `dyn PgMetadataLookup`.
 
-* Diesel's migration framework was rewritten from the ground. Existing migrations continue to 
+* Diesel's migration framework was rewritten from the ground. Existing migrations continue to
   be compatible with the rewrite, but code calling into `diesel_migrations` requires an update.
   See the [migration guide](2-0-migration) for details.
 
 * `eq_any()` now emits a `= ANY()` expression for the postgresql backend instead of `IN()`
 * `ne_all()` now emits a `!= ALL()` expression for the postgresql backend instead of `NOT IN()`
-* The sqlite backend now uses a single batch insert statement if there are now default values present 
+* The sqlite backend now uses a single batch insert statement if there are now default values present
   in the values clause
 
 * The MySQL connection is using the CLIENT_FOUND_ROWS from now on. This means that updating rows without changing any values will return the number of matched rows (like most other SQL servers do), as opposed to the number of changed rows.
 
-* The definition of `ToSql::to_sql` and `QueryFragment::walk_ast` has changed to allow serializing values without 
+* The definition of `ToSql::to_sql` and `QueryFragment::walk_ast` has changed to allow serializing values without
   copying the value itself. This is useful for database backends like sqlite where you can directly share a buffer
-  with the database. Beside of the changed signature, existing impls of this trait should remain unchanged in almost 
+  with the database. Beside of the changed signature, existing impls of this trait should remain unchanged in almost
   all cases.
 
 * The `PIPES_AS_CONCAT` sql_mode is no longer set
@@ -342,14 +343,14 @@ queries or set `PIPES_AS_CONCAT` manually.
 * We've refactored our type level representation of nullable values. This allowed us to
   fix multiple long standing bugs regarding the correct handling of nullable values in some
   corner cases (#104, #2274)
-  
+
 * Parenthesis are now inserted around all infix operations provided by diesel's `ExpressionMethods` traits
 
 * Queries containing a `distinct on` clause check now on compile time that a compatible order clause was set.
 
 * Implementations of custom SQLite SQL functions now check for panics
 
-* `diesel print-schema` now generates `Array<Nullable<ST>>` rather than `Array<ST>` for Postgres Array types. Existence of 
+* `diesel print-schema` now generates `Array<Nullable<ST>>` rather than `Array<ST>` for Postgres Array types. Existence of
   `NULL` values in database arrays would previously result in deserialization errors. Non-nullable arrays are now opt
   in (by schema patching).
 
@@ -363,8 +364,8 @@ queries or set `PIPES_AS_CONCAT` manually.
 
 * `diesel::pg::upsert` has been deprecated to support upsert queries on more than one backend.
   Please use `diesel::upsert` instead.
-  
-* `diesel::dsl::any` and `diesel::dsl::all` are now deprecated in 
+
+* `diesel::dsl::any` and `diesel::dsl::all` are now deprecated in
    favour of `ExpressionMethods::eq_any()` and `ExpressionMethods::ne_all()`
 
 
diff --git a/diesel_cli/Cargo.toml b/diesel_cli/Cargo.toml
index a9d2ba62f3f4..05354ffabc87 100644
--- a/diesel_cli/Cargo.toml
+++ b/diesel_cli/Cargo.toml
@@ -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"] }
diff --git a/diesel_cli/src/cli.rs b/diesel_cli/src/cli.rs
index 25bf28de6028..c35fd86568c6 100644
--- a/diesel_cli/src/cli.rs
+++ b/diesel_cli/src/cli.rs
@@ -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)
@@ -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")
diff --git a/diesel_cli/src/config.rs b/diesel_cli/src/config.rs
index 7a886aef3d4d..889b62317a6d 100644
--- a/diesel_cli/src/config.rs
+++ b/diesel_cli/src/config.rs
@@ -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 {
diff --git a/diesel_cli/src/infer_schema_internals/inference.rs b/diesel_cli/src/infer_schema_internals/inference.rs
index cf7650e0573c..2e95ebbb5c1f 100644
--- a/diesel_cli/src/infer_schema_internals/inference.rs
+++ b/diesel_cli/src/infer_schema_internals/inference.rs
@@ -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};
@@ -185,10 +185,15 @@ fn get_column_information(
 fn determine_column_type(
     attr: &ColumnInformation,
     conn: &mut InferConnection,
+    #[allow(unused_variables)] table: &TableName,
+    #[allow(unused_variables)] primary_keys: &[String],
+    #[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, primary_keys, config)
+        }
         #[cfg(feature = "postgres")]
         InferConnection::Pg(ref mut conn) => {
             use crate::infer_schema_internals::information_schema::DefaultSchema;
@@ -259,11 +264,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 => {
@@ -272,15 +276,11 @@ pub fn load_table_data(
     };
 
     let primary_key = get_primary_keys(connection, &name)?;
-    let primary_key = primary_key
-        .iter()
-        .map(|k| rust_name_for_sql_name(k))
-        .collect();
 
-    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, &primary_key, config)?;
 
             let ColumnInformation {
                 column_name,
@@ -298,6 +298,11 @@ pub fn load_table_data(
         })
         .collect::<Result<_, crate::errors::Error>>()?;
 
+    let primary_key = primary_key
+        .iter()
+        .map(|k| rust_name_for_sql_name(k))
+        .collect::<Vec<_>>();
+
     Ok(TableData {
         name,
         primary_key,
diff --git a/diesel_cli/src/infer_schema_internals/sqlite.rs b/diesel_cli/src/infer_schema_internals/sqlite.rs
index 272a462c10e2..dd2f9276ff7b 100644
--- a/diesel_cli/src/infer_schema_internals/sqlite.rs
+++ b/diesel_cli/src/infer_schema_internals/sqlite.rs
@@ -1,3 +1,5 @@
+use std::fmt;
+
 use diesel::deserialize::{self, Queryable};
 use diesel::dsl::sql;
 use diesel::row::NamedRow;
@@ -7,6 +9,7 @@ use diesel::*;
 
 use super::data_structures::*;
 use super::table_data::TableName;
+use crate::config::PrintSchema;
 use crate::print_schema::ColumnSorting;
 
 table! {
@@ -100,6 +103,12 @@ impl SqliteVersion {
     }
 }
 
+impl fmt::Display for SqliteVersion {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
+    }
+}
+
 fn get_sqlite_version(conn: &mut SqliteConnection) -> QueryResult<SqliteVersion> {
     let query = "SELECT sqlite_version()";
     let result = sql::<sql_types::Text>(query).load::<String>(conn)?;
@@ -204,6 +213,55 @@ 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,
+    primary_keys: &[String],
+    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(format!(
+            "Parameter `sqlite_integer_primary_key_is_bigint` needs SQLite 3.37 or above. \
+            Your current SQLite version is {sqlite_version}."
+        )));
+    }
+
+    if type_name != "integer" {
+        return Ok(false);
+    }
+
+    if !matches!(primary_keys, [pk] if pk == 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,
@@ -260,8 +318,14 @@ 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,
+    primary_keys: &[String],
+    config: &PrintSchema,
+) -> Result<ColumnType, crate::errors::Error> {
     let mut type_name = attr.type_name.to_lowercase();
     if type_name == "generated always" {
         type_name.clear();
@@ -274,7 +338,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, primary_keys, &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) {
@@ -475,3 +549,113 @@ 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;
+    }
+
+    let test_data = [
+        (
+            "table_1",
+            "CREATE TABLE table_1 (id INTEGER PRIMARY KEY)",
+            vec![("id", "Integer".to_owned())],
+            vec![("id", "BigInt".to_owned())],
+        ),
+        (
+            "table_2",
+            "CREATE TABLE table_2 (id INTEGER, PRIMARY KEY(id))",
+            vec![("id", "Integer".to_owned())],
+            vec![("id", "BigInt".to_owned())],
+        ),
+        (
+            "table_3",
+            "CREATE TABLE table_3 (id INTEGER)",
+            vec![
+                ("rowid", "Integer".to_owned()),
+                ("id", "Integer".to_owned()),
+            ],
+            vec![("rowid", "BigInt".to_owned()), ("id", "Integer".to_owned())],
+        ),
+        (
+            "table_4",
+            "CREATE TABLE table_4 (id1 INTEGER, id2 INTEGER, PRIMARY KEY(id1, id2))",
+            vec![("id1", "Integer".to_owned()), ("id2", "Integer".to_owned())],
+            vec![("id1", "Integer".to_owned()), ("id2", "Integer".to_owned())],
+        ),
+        (
+            "table_5",
+            "CREATE TABLE table_5 (id INT PRIMARY KEY)",
+            vec![("id", "Integer".to_owned())],
+            vec![("id", "Integer".to_owned())],
+        ),
+        (
+            "table_6",
+            "CREATE TABLE table_6 (id INTEGER PRIMARY KEY) WITHOUT ROWID",
+            vec![("id", "Integer".to_owned())],
+            vec![("id", "Integer".to_owned())],
+        ),
+    ];
+
+    for (table_name, sql_query, expected_off_types, expected_on_types) in test_data {
+        diesel::sql_query(sql_query).execute(&mut conn).unwrap();
+
+        let table = TableName::from_name(table_name);
+        let column_infos = get_table_data(&mut conn, &table, &Default::default()).unwrap();
+
+        let primary_keys = get_primary_keys(&mut conn, &table).unwrap();
+
+        let off_column_types = column_infos
+            .iter()
+            .map(|column_info| {
+                (
+                    column_info.column_name.as_str(),
+                    determine_column_type(
+                        &mut conn,
+                        column_info,
+                        &table,
+                        &primary_keys,
+                        &Default::default(),
+                    )
+                    .unwrap()
+                    .sql_name,
+                )
+            })
+            .collect::<Vec<_>>();
+
+        let on_column_types = column_infos
+            .iter()
+            .map(|column_info| {
+                (
+                    column_info.column_name.as_str(),
+                    determine_column_type(
+                        &mut conn,
+                        column_info,
+                        &table,
+                        &primary_keys,
+                        &PrintSchema {
+                            sqlite_integer_primary_key_is_bigint: Some(true),
+                            ..Default::default()
+                        },
+                    )
+                    .unwrap()
+                    .sql_name,
+                )
+            })
+            .collect::<Vec<_>>();
+
+        assert_eq!(
+            (table_name, off_column_types),
+            (table_name, expected_off_types)
+        );
+
+        assert_eq!(
+            (table_name, on_column_types),
+            (table_name, expected_on_types)
+        );
+    }
+}
diff --git a/diesel_cli/src/main.rs b/diesel_cli/src/main.rs
index a0ae06a971a3..f8cc868c43bd 100644
--- a/diesel_cli/src/main.rs
+++ b/diesel_cli/src/main.rs
@@ -296,6 +296,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(())
 }
diff --git a/diesel_cli/src/migrations/diff_schema.rs b/diesel_cli/src/migrations/diff_schema.rs
index 08c05f15d52d..d55d4e610979 100644
--- a/diesel_cli/src/migrations/diff_schema.rs
+++ b/diesel_cli/src/migrations/diff_schema.rs
@@ -9,13 +9,13 @@ use std::io::Read;
 use std::path::Path;
 use syn::visit::Visit;
 
-use crate::config::Config;
+use crate::config::{Config, PrintSchema};
 use crate::database::InferConnection;
 use crate::infer_schema_internals::{
     filter_table_names, load_table_names, ColumnDefinition, ColumnType, ForeignKeyConstraint,
     TableData, TableName,
 };
-use crate::print_schema::DocConfig;
+use crate::print_schema::{ColumnSorting, DocConfig};
 
 fn compatible_type_list() -> HashMap<&'static str, Vec<&'static str>> {
     let mut map = HashMap::new();
@@ -32,7 +32,7 @@ pub fn generate_sql_based_on_diff_schema(
     matches: &ArgMatches,
     schema_file_path: &Path,
 ) -> Result<(String, String), crate::errors::Error> {
-    let config = config.set_filter(matches)?;
+    let mut config = config.set_filter(matches)?;
 
     let project_root = crate::find_project_root()?;
 
@@ -88,6 +88,21 @@ pub fn generate_sql_based_on_diff_schema(
         expected_schema_map.insert(t.table_name.to_string(), t);
     }
 
+    config.print_schema.with_docs = DocConfig::NoDocComments;
+    config.print_schema.column_sorting = ColumnSorting::OrdinalPosition;
+
+    // Parameter `sqlite_integer_primary_key_is_bigint` is only used for a SQLite connection
+    match conn {
+        #[cfg(feature = "postgres")]
+        InferConnection::Pg(_) => config.print_schema.sqlite_integer_primary_key_is_bigint = None,
+        #[cfg(feature = "sqlite")]
+        InferConnection::Sqlite(_) => (),
+        #[cfg(feature = "mysql")]
+        InferConnection::Mysql(_) => {
+            config.print_schema.sqlite_integer_primary_key_is_bigint = None;
+        }
+    }
+
     let mut schema_diff = Vec::new();
 
     for table in tables_from_database {
@@ -95,8 +110,7 @@ pub fn generate_sql_based_on_diff_schema(
         let columns = crate::infer_schema_internals::load_table_data(
             &mut conn,
             table.clone(),
-            &crate::print_schema::ColumnSorting::OrdinalPosition,
-            DocConfig::NoDocComments,
+            &config.print_schema,
         )?;
         if let Some(t) = expected_schema_map.remove(&table.sql_name.to_lowercase()) {
             tracing::info!(table = ?t.sql_name, "Table exists in schema.rs");
@@ -208,19 +222,19 @@ pub fn generate_sql_based_on_diff_schema(
             #[cfg(feature = "postgres")]
             InferConnection::Pg(_) => {
                 let mut qb = diesel::pg::PgQueryBuilder::default();
-                diff.generate_up_sql(&mut qb)?;
+                diff.generate_up_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
             #[cfg(feature = "sqlite")]
             InferConnection::Sqlite(_) => {
                 let mut qb = diesel::sqlite::SqliteQueryBuilder::default();
-                diff.generate_up_sql(&mut qb)?;
+                diff.generate_up_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
             #[cfg(feature = "mysql")]
             InferConnection::Mysql(_) => {
                 let mut qb = diesel::mysql::MysqlQueryBuilder::default();
-                diff.generate_up_sql(&mut qb)?;
+                diff.generate_up_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
         };
@@ -229,19 +243,19 @@ pub fn generate_sql_based_on_diff_schema(
             #[cfg(feature = "postgres")]
             InferConnection::Pg(_) => {
                 let mut qb = diesel::pg::PgQueryBuilder::default();
-                diff.generate_down_sql(&mut qb)?;
+                diff.generate_down_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
             #[cfg(feature = "sqlite")]
             InferConnection::Sqlite(_) => {
                 let mut qb = diesel::sqlite::SqliteQueryBuilder::default();
-                diff.generate_down_sql(&mut qb)?;
+                diff.generate_down_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
             #[cfg(feature = "mysql")]
             InferConnection::Mysql(_) => {
                 let mut qb = diesel::mysql::MysqlQueryBuilder::default();
-                diff.generate_down_sql(&mut qb)?;
+                diff.generate_down_sql(&mut qb, &config.print_schema)?;
                 qb.finish()
             }
         };
@@ -311,6 +325,7 @@ impl SchemaDiff {
     fn generate_up_sql<DB>(
         &self,
         query_builder: &mut impl QueryBuilder<DB>,
+        config: &PrintSchema,
     ) -> Result<(), crate::errors::Error>
     where
         DB: Backend,
@@ -352,12 +367,18 @@ impl SchemaDiff {
                         )
                     })
                     .collect::<Vec<_>>();
+
+                let sqlite_integer_primary_key_is_bigint = config
+                    .sqlite_integer_primary_key_is_bigint
+                    .unwrap_or_default();
+
                 generate_create_table(
                     query_builder,
                     table,
                     &column_data,
                     &primary_keys,
                     &foreign_keys,
+                    sqlite_integer_primary_key_is_bigint,
                 )?;
             }
             SchemaDiff::ChangeTable {
@@ -390,7 +411,11 @@ impl SchemaDiff {
         Ok(())
     }
 
-    fn generate_down_sql<DB>(&self, query_builder: &mut impl QueryBuilder<DB>) -> QueryResult<()>
+    fn generate_down_sql<DB>(
+        &self,
+        query_builder: &mut impl QueryBuilder<DB>,
+        config: &PrintSchema,
+    ) -> QueryResult<()>
     where
         DB: Backend,
     {
@@ -411,12 +436,17 @@ impl SchemaDiff {
                     })
                     .collect::<Vec<_>>();
 
+                let sqlite_integer_primary_key_is_bigint = config
+                    .sqlite_integer_primary_key_is_bigint
+                    .unwrap_or_default();
+
                 generate_create_table(
                     query_builder,
                     &table.sql_name.to_lowercase(),
                     &columns.column_data,
                     &columns.primary_key,
                     &fk,
+                    sqlite_integer_primary_key_is_bigint,
                 )?;
             }
             SchemaDiff::CreateTable { to_create, .. } => {
@@ -428,6 +458,9 @@ impl SchemaDiff {
                 removed_columns,
                 changed_columns,
             } => {
+                // We don't need to check the `sqlite_integer_primary_key_is_bigint` parameter here
+                // since `ÀLTER TABLE` queries cannot modify primary key columns in SQLite.
+                // See https://www.sqlite.org/lang_altertable.html#alter_table_add_column for more information.
                 for c in added_columns
                     .iter()
                     .chain(changed_columns.iter().map(|(_, b)| b))
@@ -498,6 +531,7 @@ fn generate_create_table<DB>(
     column_data: &[ColumnDefinition],
     primary_keys: &[String],
     foreign_keys: &[(String, String, String)],
+    sqlite_integer_primary_key_is_bigint: bool,
 ) -> QueryResult<()>
 where
     DB: Backend,
@@ -514,11 +548,35 @@ where
             query_builder.push_sql(",\n");
         }
         query_builder.push_sql("\t");
+
+        let is_only_primary_key =
+            primary_keys.contains(&column.rust_name) && primary_keys.len() == 1;
+
         query_builder.push_identifier(&column.sql_name)?;
-        generate_column_type_name(query_builder, &column.ty);
-        if primary_keys.contains(&column.rust_name) && primary_keys.len() == 1 {
+
+        // When the `sqlite_integer_primary_key_is_bigint` config parameter is used,
+        // if a column is the only primary key and its type is `BigInt`,
+        // we consider it equivalent to the `rowid` column in order to be compatible
+        // with the `print-schema` command using the same config parameter.
+        // See https://www.sqlite.org/lang_createtable.html#rowid for more information.
+        if sqlite_integer_primary_key_is_bigint
+            && is_only_primary_key
+            && column.ty.sql_name.eq_ignore_ascii_case("BigInt")
+        {
+            let ty = ColumnType {
+                rust_name: "Integer".into(),
+                sql_name: "Integer".into(),
+                ..column.ty.clone()
+            };
+            generate_column_type_name(query_builder, &ty);
+        } else {
+            generate_column_type_name(query_builder, &column.ty);
+        }
+
+        if is_only_primary_key {
             query_builder.push_sql(" PRIMARY KEY");
         }
+
         if let Some((table, _, pk)) = foreign_keys.iter().find(|(_, k, _)| k == &column.rust_name) {
             foreign_key_list.push((column, table, pk));
         }
diff --git a/diesel_cli/src/migrations/mod.rs b/diesel_cli/src/migrations/mod.rs
index f4785795594b..0d529e354086 100644
--- a/diesel_cli/src/migrations/mod.rs
+++ b/diesel_cli/src/migrations/mod.rs
@@ -91,12 +91,15 @@ pub(super) fn run_migration_command(matches: &ArgMatches) -> Result<(), crate::e
 
             let (up_sql, down_sql) = if let Some(diff_schema) = args.get_one::<String>("SCHEMA_RS")
             {
-                let config = Config::read(matches)?;
+                let mut config = Config::read(matches)?;
                 let diff_schema = if diff_schema == "NOT_SET" {
                     config.print_schema.file.clone()
                 } else {
                     Some(PathBuf::from(diff_schema))
                 };
+                if args.get_flag("sqlite-integer-primary-key-is-bigint") {
+                    config.print_schema.sqlite_integer_primary_key_is_bigint = Some(true);
+                }
                 if let Some(diff_schema) = diff_schema {
                     self::diff_schema::generate_sql_based_on_diff_schema(
                         config,
diff --git a/diesel_cli/src/print_schema.rs b/diesel_cli/src/print_schema.rs
index 5217192dd345..76e6573e5187 100644
--- a/diesel_cli/src/print_schema.rs
+++ b/diesel_cli/src/print_schema.rs
@@ -157,7 +157,7 @@ pub fn output_schema(
         remove_unsafe_foreign_keys_for_codegen(connection, &foreign_keys, &table_names);
     let table_data = table_names
         .into_iter()
-        .map(|t| load_table_data(connection, t, &config.column_sorting, config.with_docs))
+        .map(|t| load_table_data(connection, t, config))
         .collect::<Result<Vec<_>, crate::errors::Error>>()?;
 
     let mut out = String::new();


Test patch:
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/down.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/down.sql/expected.snap
new file mode 100644
index 000000000000..6bf6dc40cefc
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/down.sql/expected.snap
@@ -0,0 +1,7 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+-- This file should undo anything in `up.sql`
+DROP TABLE IF EXISTS `users`;
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/initial_schema.sql b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/initial_schema.sql
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/initial_schema.sql
@@ -0,0 +1,1 @@
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/schema_out.rs/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/schema_out.rs/expected.snap
new file mode 100644
index 000000000000..a7c2e4fa010d
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/schema_out.rs/expected.snap
@@ -0,0 +1,12 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+// @generated automatically by Diesel CLI.
+
+diesel::table! {
+    users (id) {
+        id -> Bigint,
+        name -> Text,
+    }
+}
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/up.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/up.sql/expected.snap
new file mode 100644
index 000000000000..f384ae4bfca8
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/mysql/up.sql/expected.snap
@@ -0,0 +1,9 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+-- Your SQL goes here
+CREATE TABLE `users`(
+	`id` BIGINT NOT NULL PRIMARY KEY,
+	`name` TEXT NOT NULL
+);
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/down.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/down.sql/expected.snap
new file mode 100644
index 000000000000..432f142620b2
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/down.sql/expected.snap
@@ -0,0 +1,8 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+assertion_line: 300
+description: "Test: diff_add_table"
+---
+-- This file should undo anything in `up.sql`
+DROP TABLE IF EXISTS "users";
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/initial_schema.sql b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/initial_schema.sql
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/initial_schema.sql
@@ -0,0 +1,1 @@
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/schema_out.rs/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/schema_out.rs/expected.snap
new file mode 100644
index 000000000000..cb7df36b6aec
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/schema_out.rs/expected.snap
@@ -0,0 +1,13 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+assertion_line: 323
+description: "Test: diff_add_table"
+---
+// @generated automatically by Diesel CLI.
+
+diesel::table! {
+    users (id) {
+        id -> Int8,
+        name -> Text,
+    }
+}
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/up.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/up.sql/expected.snap
new file mode 100644
index 000000000000..561ed4c34775
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/postgres/up.sql/expected.snap
@@ -0,0 +1,10 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+assertion_line: 295
+description: "Test: diff_add_table"
+---
+-- Your SQL goes here
+CREATE TABLE "users"(
+	"id" BIGINT NOT NULL PRIMARY KEY,
+	"name" TEXT NOT NULL
+);
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/schema.rs b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/schema.rs
new file mode 100644
index 000000000000..bcb1d8b16565
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/schema.rs
@@ -0,0 +1,6 @@
+table! {
+    users {
+        id -> BigInt,
+        name -> Text,
+    }
+}
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/down.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/down.sql/expected.snap
new file mode 100644
index 000000000000..6bf6dc40cefc
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/down.sql/expected.snap
@@ -0,0 +1,7 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+-- This file should undo anything in `up.sql`
+DROP TABLE IF EXISTS `users`;
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/initial_schema.sql b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/initial_schema.sql
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/initial_schema.sql
@@ -0,0 +1,1 @@
+
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/schema_out.rs/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/schema_out.rs/expected.snap
new file mode 100644
index 000000000000..0f8d10baa63e
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/schema_out.rs/expected.snap
@@ -0,0 +1,12 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+// @generated automatically by Diesel CLI.
+
+diesel::table! {
+    users (id) {
+        id -> BigInt,
+        name -> Text,
+    }
+}
diff --git a/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/up.sql/expected.snap b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/up.sql/expected.snap
new file mode 100644
index 000000000000..a53abfe52623
--- /dev/null
+++ b/diesel_cli/tests/generate_migrations/diff_add_table_sqlite_rowid_column/sqlite/up.sql/expected.snap
@@ -0,0 +1,10 @@
+---
+source: diesel_cli/tests/migration_generate.rs
+description: "Test: diff_add_table"
+---
+-- Your SQL goes here
+CREATE TABLE `users`(
+	`id` INTEGER NOT NULL PRIMARY KEY,
+	`name` TEXT NOT NULL
+);
+
diff --git a/diesel_cli/tests/migration_generate.rs b/diesel_cli/tests/migration_generate.rs
index 1c6deafa663b..fd2169b24615 100644
--- a/diesel_cli/tests/migration_generate.rs
+++ b/diesel_cli/tests/migration_generate.rs
@@ -220,6 +220,14 @@ fn migration_generate_from_diff_add_table() {
     test_generate_migration("diff_add_table", Vec::new());
 }
 
+#[test]
+fn migration_generate_from_diff_add_table_sqlite_rowid_column() {
+    test_generate_migration(
+        "diff_add_table_sqlite_rowid_column",
+        vec!["--sqlite-integer-primary-key-is-bigint"],
+    );
+}
+
 #[test]
 fn migration_generate_from_diff_drop_alter_table_add_column() {
     test_generate_migration("diff_alter_table_add_column", Vec::new());
diff --git a/diesel_cli/tests/print_schema.rs b/diesel_cli/tests/print_schema.rs
index dd298713b81a..e867a2e09edf 100644
--- a/diesel_cli/tests/print_schema.rs
+++ b/diesel_cli/tests/print_schema.rs
@@ -250,6 +250,15 @@ fn print_schema_generated_columns_with_generated_always() {
     test_print_schema("print_schema_generated_columns_generated_always", vec![])
 }
 
+#[test]
+#[cfg(feature = "sqlite")]
+fn print_schema_sqlite_rowid_column() {
+    test_print_schema(
+        "print_schema_sqlite_rowid_column",
+        vec!["--sqlite-integer-primary-key-is-bigint"],
+    )
+}
+
 #[test]
 #[cfg(feature = "postgres")]
 fn print_schema_multiple_annotations() {
diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/diesel.toml b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/diesel.toml
new file mode 100644
index 000000000000..e914c1be0c32
--- /dev/null
+++ b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/diesel.toml
@@ -0,0 +1,4 @@
+[print_schema]
+file = "src/schema.rs"
+with_docs = false
+sqlite_integer_primary_key_is_bigint = true
diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/expected.snap b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/expected.snap
new file mode 100644
index 000000000000..543c6a8a50df
--- /dev/null
+++ b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/expected.snap
@@ -0,0 +1,30 @@
+---
+source: diesel_cli/tests/print_schema.rs
+description: "Test: print_schema_sqlite_rowid_column"
+---
+// @generated automatically by Diesel CLI.
+
+diesel::table! {
+    users1 (id) {
+        id -> BigInt,
+    }
+}
+
+diesel::table! {
+    users2 (id) {
+        id -> Integer,
+    }
+}
+
+diesel::table! {
+    users3 (rowid) {
+        rowid -> BigInt,
+        name -> Text,
+    }
+}
+
+diesel::allow_tables_to_appear_in_same_query!(
+    users1,
+    users2,
+    users3,
+);
diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/schema.sql b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/schema.sql
new file mode 100644
index 000000000000..21c107a7a110
--- /dev/null
+++ b/diesel_cli/tests/print_schema/print_schema_sqlite_rowid_column/sqlite/schema.sql
@@ -0,0 +1,3 @@
+CREATE TABLE users1 (id INTEGER NOT NULL PRIMARY KEY);
+CREATE TABLE users2 (id INTEGER NOT NULL PRIMARY KEY) WITHOUT ROWID;
+CREATE TABLE users3 (name TEXT NOT NULL);