Skip to content

Autoincrement 2 - #119

Merged
krlmlr merged 17 commits into
r-dbi:masterfrom
wibeasley:autoincrement-2
Feb 2, 2016
Merged

Autoincrement 2#119
krlmlr merged 17 commits into
r-dbi:masterfrom
wibeasley:autoincrement-2

Conversation

@wibeasley

Copy link
Copy Markdown
Contributor

(Hi @krlmlr, this PR replaces #58)

Can someone confirm that this is the intended behavior? I'd hate to be writing tests that approve an unintended effect.

When the autoincrement column isn't in the data.frame sent to the database, it's populated by SQLite.

But when it is populated before getting to the database, SQLite won't assign/update values. And when retrieved from SQLite, the data.frame will be re-ordered (corresponding to the values in the (ignored) autoincrement column).

> ds_local
    id name score
1  126    a     1
2  125    b     2
3  124    c     3
...
24 103    x    24
25 102    y    25
26 101    z    26

> dbWriteTable(con, name = 'tbl', value = ds_local, append = TRUE, row.names = FALSE)
> ds_remote <- dbReadTable(con, "tbl")

> ds_remote
    id name score
1  101    z    26
2  102    y    25
3  103    x    24
...
24 124    c     3
25 125    b     2
26 126    a     1

@krlmlr

krlmlr commented Nov 13, 2015

Copy link
Copy Markdown
Member

SQL tables don't have an intrinsic order unless you supply ORDER BY with the query (which dbReadTable() doesn't). So the SQL engine is free to do whatever it wants to in this case.

I would expect the following behavior:

  • If the autoincrement column doesn't exist in the data frame passed to dbWriteTable(), it's populated by the SQL engine
  • If it exists, the values are taken from the data; NA values should be converted to explicit NULL in this case, and it's up to the SQL engine to decide if further conversion happens there

Does this align with your observations?

@wibeasley

Copy link
Copy Markdown
Contributor Author

@krlmlr, yes, that is consistent. And to be specific on the second bullet, the engine apparently decides to autopopulate the column of NAs (which makes sense to me).

>   ds_local <- data.frame(
+     #Notice the 'id' column is set locally, which overrides the autoincrement assignment in the DB.
+     id               = NA_integer_,
+     name             = letters,
+     score            = 1:26,
+     stringsAsFactors = FALSE
+   )

>   dbWriteTable(con, name = 'tbl', value = ds_local, append = TRUE, row.names = FALSE)
[1] TRUE
>   
>   ds_remote <- dbReadTable(con, "tbl")
>   ds_remote
   id name score
1   1    a     1
2   2    b     2
3   3    c     3
...
24 24    x    24
25 25    y    25
26 26    z    26

@krlmlr

krlmlr commented Nov 13, 2015

Copy link
Copy Markdown
Member

Changes minus roxygen noise: 7997aca...df4d55d

Comment thread R/table.R Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also apply for append = TRUE?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to have the column in the data frame if the table already exists. Your text seems to imply otherwise.

@wibeasley

Copy link
Copy Markdown
Contributor Author

@krlmlr

  • I rewrote some of the tests so labels aren't necessary. I kept the labels in other places where the intent is less clear without it (and I couldn't think of a way to rewrite it).
  • Datasets are now sorted before the tests (in response to https://github.com/rstats-db/RSQLite/pull/119/files#r44763094)
  • Two more scenarios are tested (where the id has some values and some NAs). As before, I think everything is working as people intended. The new tests are included more for the sake of detecting future regressions.

Question:
In response to this question of yours, do you want the append=TRUE and append=FALSE comparisons to be within the same test/function? In other words, should the function "autoincrement correctly populated by database" address both scenarios? Or should they have separate set ups?

I'd favor separation/isolation, even though it would increase the amount of code. But I wanted to check with you guys first, since you guys tend to have strong opinions about these things.

Comment thread tests/testthat/test-dbWriteTable.R Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"in reverse order": There's no intrinsic order. Same above.

@krlmlr

krlmlr commented Nov 13, 2015

Copy link
Copy Markdown
Member

Very thorough tests, thanks! Isolated tests are fine with me.

Could you please also add a bullet to NEWS.md?

@wibeasley

Copy link
Copy Markdown
Contributor Author
  • "in reverse order": There's no intrinsic order. Same above.

    That test description was intended to communicate that the id assignment in the local dataset was (approximately) reverse ordered ...and thus exposed the potential for a sequential-assigning-database to duplicate previous values. I see your point, and will try to think of a less ambiguous phrase. In the meantime, I'm open to suggestions if one has already occurred to you.

    c(NA_integer_, 306, NA_integer_, -204, 103, 102, NA_integer_)
  • You don't need to have the column in the data frame if the table already exists. Your text seems to imply otherwise.

    Agreed. My comfort and understanding of this functionality is a lot different than it was a year ago when I wrote the description. I'll update the documentation after I write the append=FALSE tests. I may learn something that informs the documentation and makes it clearer.

  • Could you please also add a bullet to NEWS.md?

    Sure, I'd forgotten about that. Thanks for being so polite while making good suggestions.

This (almost) empty file is so Git's renaming decision isn't tricked in the next commit.
A test only declares input & expected output.  The helper function does the real work.
I didn't realize SQLite supports autoincrement [only on a primary key](https://www.sqlite.org/autoinc.html).

"Any attempt to use AUTOINCREMENT ... on a column other than the INTEGER PRIMARY KEY column results in an error."
@wibeasley

Copy link
Copy Markdown
Contributor Author

@krlmlr, I believe I've made all the modifications you've requested. Please tell me if there's something else I can do.

I didn't add any tests for append=FALSE in this PR because it's not possible unless overwrite is true, and I assume at autoincrement DDL is irrelevant when overwriting. I have started a batch of overwrite tests, but I think those are different enough to justify a different PR.

@krlmlr

krlmlr commented Nov 21, 2015

Copy link
Copy Markdown
Member

Thanks, looks good to me. I think overwrite = TRUE tests are generic enough for the new DBItest package (r-dbi/DBItest#12).

@hadley: Can we merge this? See changes minus roxygen noise: 7997aca...df4d55d

krlmlr added a commit that referenced this pull request Feb 2, 2016
Additional documentation and unit tests for autoincrement keys (#119, @wibeasley).
@krlmlr
krlmlr merged commit 86fee9c into r-dbi:master Feb 2, 2016
@krlmlr

krlmlr commented Feb 2, 2016

Copy link
Copy Markdown
Member

Thanks!

krlmlr added a commit that referenced this pull request Nov 30, 2016
- New maintainer: Kirill Müller.

- RSQLite always builds with the included source, which is located in `src/sqlite3`. This prevents bugs due to API mismatches and considerably simplifies the build process.

- Current version: 3.11.1.

- Enable JSON1 extension (#152, @TigerToes).

- Include support for FTS5 (@mkuhn).

- Compilation limits `SQLITE_MAX_VARIABLE_NUMBER` and `SQLITE_MAX_COLUMN` have been reset to the defaults. The documentation suggests setting to such high values is a bad idea.

- Header files for `sqlite3` are no longer installed, linking to the package is not possible anymore. Packages that require access to the low-level sqlite3 API should bundle their own copy.

- `RSQLite()` no longer automatically attaches DBI when loaded. This is to
  encourage you to use `library(DBI); dbConnect(RSQLite::SQLite())`.

- Functions that take a table name, such as `dbWriteTable()` and `dbReadTable()`,
  now quote the table name via `dbQuoteIdentifier()`.
  This means that caller-quoted names should be marked as such with `DBI::SQL()`.

- RSQLite has been rewritten (essentially from scratch) in C++ with
  Rcpp. This has considerably reduced the amount of code, and allows us to
  take advantage of the more sophisticated memory management tools available in
  Rcpp. This rewrite should yield some minor performance improvements, but
  most importantly protect against memory leaks and crashes. It also provides
  a better base for future development. In particular, it is now technically
  possible to have multiple result sets per connection, although this feature
  is currently disabled (#150).

- You can now use SQLite's URL specification for databases. This allows you to
  create [shared in-memory](https://www.sqlite.org/inmemorydb.html) databases
  (#70).

- Queries (#69), query parameters and table data are always converted to UTF-8 before being sent to the database.

- Adapted to `DBI` 0.5, new code should use `dbExecute()` instead of `dbGetQuery()`, and `dbSendStatement()` instead of `dbSendQuery()` where appropriate.

- New strategy for prepared queries. Create a prepared query with `dbSendQuery()` or `dbSendStatement()` and bind values with `dbBind()`. The same query/statement can be executed efficiently multiple times by passing a data-frame-like object (#168, #178, #181).

- `dbSendQuery()`, `dbGetQuery()`, `dbSendStatement()` and `dbExecute()`
  also support inline parameterised queries,
  like `dbGetQuery(datasetsDb(), "SELECT * FROM mtcars WHERE cyl = :cyl",
  params = list(cyl = 4))`. This has no performance benefits but protects you
  from SQL injection attacks.

- Improve column type inference: the first non-`NULL` value decides the type of a column (#111). If there are no non-`NULL` values, the column affinity is used, determined according to sqlite3 rules (#160).

- `dbFetch()` uses the same row name strategy as `dbReadTable()` (#53).

- `dbColumnInfo()` will now return information even before you've retrieved any data.

- New `sqliteVersion()` prints the header and library versions of RSQLite.

- Deprecation warnings are given only once, with a clear reference to the source.

- `datasetsDb()` now returns a read-only database, to avoid modifications to the installed file.

- `make.db.names()` has been formally deprecated. Please use `dbQuoteIdentifier()` instead. This function is also used in `dbReadTable()`, `dbRemoveTable()`, and `dbListFields()` (#106, #132).

- `sqliteBuildTableDefinition()` has been deprecated. Use `DBI::sqlCreateTable()` instead.

- `dbGetException()` now raises a deprecation warning and always returns `list(errorNum = 0L, errorMsg = "OK")`, because querying the last SQLite error only works if an error actually occurred (#129).

- `dbSendPreparedQuery()` and `dbGetPreparedQuery()` have been reimplemented (with deprecation warning) using `dbSendQuery()`, `dbBind()` and `dbFetch()` for compatibility with existing packages (#100, #153, #168, #181). Please convert to the new API, because the old function may be removed completely very soon: They were never part of the official API, and do less argument checking than the new APIs. Both `dbSendPreparedQuery()` and `dbGetPreparedQuery()` ignore parameters not found in the query, with a warning (#174).

- Reimplemented `dbListResults()` (with deprecation warning) for compatibility with existing packages (#154).

- Soft-deprecated `dbGetInfo()`: The "Result" method is implemented by DBI, the methods for the other classes raise a warning (#137). It's now better to access the metadata with individual functions `dbHasCompleted()`, `dbGetRowCount()` and `dbGetRowsAffected()`.

- All `summary()` methods have been removed: the same information is now displayed in the `show()` methods, which were previously pretty useless.

- The `raw` data type is supported in `dbWriteTable()`, creates a `TEXT` column with a warning (#173).

- Numeric values for the `row.names` argument are converted to logical, with a warning (#170).

- If the number of data frame columns matches the number of existing columns for `dbWriteTable(append = TRUE)`, columns will be matched by position for compatibility, with a warning in case of a name mismatch (#164).

- `dbWriteTable()` supports the `field.types` argument when creating a new table (#171), and the `temporary` argument, default `FALSE` (#113).

- Reexporting `dbGetQuery()` and `dbDriver()` (#147, #148, #183).

- `sqliteCopyDatabase()` accepts character as `to` argument again, in this case a temporary connection is opened.

- Reimplemented `dbWriteTable("SQLiteConnection", "character", "character")` for import of CSV files, using a function from the old codebase (#151).

- `dbWriteTable("SQLiteConnection", "character", "data.frame")` looks
  for table names already enclosed in backticks and uses these,
  (with a warning), for compatibility with the sqldf package.

- The `dbExistsTable()` function now works faster by filtering the list of tables using SQL (#166).

- Start on a basic vignette: `vignette("RSQLite")` (#50).

- Reworked function and method documentation, removed old documentation (#121).

- Using `dbExecute()` in documentation and examples.

- Using both `":memory:"` and `":file::memory:"` in documentation.

- Added additional documentation and unit tests for
  [autoincrement keys](https://www.sqlite.org/autoinc.html) (#119, @wibeasley).

- Avoid warning about missing `long long` data type in C++98 by using a compound data type built from two 32-bit integers, with static assert that the size is 8 indeed.

- Remove all compilation warnings.

- All DBI methods contain an ellipsis `...` in their signature. Only the `name` argument to the transaction methods appears before the ellipsis for compatibility reasons.

- Using the `DBItest` package for testing (#105), with the new `constructor_relax_args` tweak.

- Using the `plogr` for logging at the C++ level, can be enabled via `RSQLite:::init_logging()`.

- Using new `sqlRownamesToColumn()` and `sqlColumnToRownames()` (r-dbi/DBI#91).

- Using `astyle` for code formatting (#159), also in tests (but only if sources can be located), stripped space at end of line in all source files.

- Tracking dependencies between source and header files (#138).

- Moved all functions from headers to modules (#162).

- Fixed all warnings in tests (#157).

- Checking message wording for deprecation warnings (#157).

- Testing simple and named transactions (#163).

- Using container-based builds and development version of `testthat` on Travis.

- Enabled AppVeyor testing.

- Differential reverse dependency checks.

- Added upgrade script for sqlite3 sources and creation script for the datasets database to the `data-raw` directory.
troels pushed a commit to troels/RSQLite that referenced this pull request Nov 27, 2019
PKG_CFLAGS and PKG_LIBS were not being set when using pg_config for i…
troels pushed a commit to troels/RSQLite that referenced this pull request Nov 27, 2019
- Update Rcpp registration code.
- `dbConnect()` now accepts arbitrary connection parameters in the `...` argument (r-dbi#83, @thrasibule).
- Handles NA values by converting them to NULL (r-dbi#82, @thrasibule).
- Handle string quoting and missing values in strings (r-dbi#89, @jimhester).
- `PKG_CFLAGS` and `PKG_LIBS` are now being set when using pg_config for `includedir` and `libdir` (r-dbi#119, @Usman-R).
- Use `BYTEA` instead of `BLOB` for PostgreSQL 9.5 support.
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants