Prefer Strict Tables in SQLite

(evanhahn.com)

118 points | by ingve 2 hours ago

12 comments

  • jll29 1 hour ago
    I'd like to see STRICT as the default.

    That's pretty much the only disagreement with the SQLite developer, who is an amazing guy that wrote an amazing tool!

    • simonw 1 hour ago
      SQLite very rarely changes defaults because of their commitment to backwards compatibility. They don't want software written against SQLite 3.53 to start throwing errors when upgraded to 3.54 because suddenly `CREATE TABLE` is creating strict tables and the rest of the software breaks as a result.
      • ezst 42 minutes ago
        Which seems reasonable. And those who care deeply will have no problem configuring it the specific way they want on their own project. Win-win.
        • sinfulprogeny 13 minutes ago
          Unless you don't know it exists of course
          • walthamstow 8 minutes ago
            Even a pure AI naysayer can use it to find obscure SQLite options
    • ahartmetz 54 minutes ago
      There are more similar issues, like disabling foreign key constraints by default "for compatibility reasons". Makes me wonder if there was a time when SQLite supported foreign key syntax, but didn't actually implement the functionality.
      • formerly_proven 43 minutes ago
        > This document describes the support for SQL foreign key constraints introduced in SQLite version 3.6.19 (2009-10-14).
        • ahartmetz 40 minutes ago
          That quote leaves open whether SQLite "pretended" to support foreign keys by allowing to create tables with them, but didn't implement them. Otherwise, I don't see the compatibility problem.
    • neverartful 37 minutes ago
      I agree with you. I'd go one step further and let it be the only mode available starting with new versions of the library.
    • mort96 1 hour ago
      Yeah it's a really weird design decision. Why would I want the database to let me accidentally insert the wrong type? SQLite is mostly great but its philosophy towards type safety leaves something to be desired. I once had to clean up in a project where someone had accidentally stored the strings '1' and '0' in a Boolean column in code deployed to thousands of devices; not fun.

      Another thing I dislike is the lack of timestamp types. Instead, you're expected to just use a text column and store a textual timestamp. Even worse, instead of using ISO, the standard date time functions produce strings on the form "yyyy-mm-dd HH:MM:SS" which you're just supposed to assume are in UTC. Why not at least give us "yyyy-mm-ddTHH:MM:SSZ"? Or, you know, a proper space efficient timestamp data type.

      A truly great project, with some truly baffling design decisions.

      • masklinn 1 hour ago
        > Instead, you're expected to just use a text column and store a textual timestamp.

        You can actually use an integer column and store Unix timestamps (or floats for subsecond accuracy).

        But yes, sqlite has very little types support and its default behaviour is very much unityped / dynamically typed which I also dislike. Same with having to enable foreign keys every time you open a connection.

        • ncruces 16 minutes ago
          Only if you're not compiling yourself, otherwise, there's SQLITE_DEFAULT_FOREIGN_KEYS=1
      • 3eb7988a1663 57 minutes ago
        '0' and '1', while not ideal, seems fine to maintain? There is not even a true SQLite boolean type.

        Then again, I have been subjected to Oracle nonsense for too long and have had to accept all of the boolean alternatives: 0,1,'0','1',Y,N,y,n,YES,NO,T,F, etc

      • formerly_proven 59 minutes ago
        If every time the SQLite team added a better default for something they changed it, we'd be at SQLite 11 by now and every application would contain at least six incompatible versions of SQLite.
      • win311fwg 19 minutes ago
        If you are using SQLite as an embedded database, which seems to be SQLite's primary use-case, why wouldn't you prove statically that you are not accidentally inserting the wrong type? Runtime checks are unnecessary overhead.

        Runtime validation is there to enable when using SQLite in other ways.

  • bch 12 minutes ago
    I had a UUID (partly?) mis-converted to a number if the UUID started w (from memory) something like 08123… which was parsed as octal. Confusing, annoying, fixed w “strict” and a complete table rebuild.
  • petilon 1 hour ago
    The downside of strict tables is that some data types are not available, such as Date.

    Strict should really be the default. If a database is shared by multiple applications then you should be able to rely on the declared data type. If one application stores a string into a numeric column that breaks everyone else.

    On the other hand, the main use case for SQLite is embedded databases. And that means only one application is using the database. In that scenario being able to evolve the schema (as opposed to creating a new database and copying the data over) can be seen as an advantage. The application's code knows what to expect in each column--including mixed data types.

    • e2le 58 minutes ago
      > The downside of strict tables is that some data types are not available, such as Date.

      There are only 5 datatypes in sqlite. INTEGER, TEXT, BLOB, REAL, and NUMERIC.

      https://sqlite.org/datatype3.html

      • ncruces 14 minutes ago
        The downside is that you loose the place to store the metadata that a column is supposed to store a date.

        Which is why I prefer not to use them.

      • masklinn 47 minutes ago
        numeric is not a type it’s an affinity, the underlying types are real and integer. That is why numeric is not valid on strict tables.
    • Ciantic 1 hour ago
      SQLite has no date data type. Also SQLite has no way to call EXPLAIN on query and get the dummy type name either for arbitrary SELECT query, so you can't even infer it, if you were to use the dummy type name "DATE" or "DATETIME".
    • masklinn 58 minutes ago
      > some data types are not available, such as Date.

      That’s not a type, you just get a numeric-affinity column.

  • cdmckay 1 hour ago
    I would’ve thought this was the default.
  • blixt 1 hour ago
    I think I can see how dynamic data types make sense (eg flat key/value store), but my question would be:

    What is least surprising? That INTEGER implicity accepts 'hello world' without error, or that you can't insert such a value unless you use a keyword like NONSTRICT or a type like ANY?

    I would wager the vast majority of SQLite users if asked would probably not expect it to work.

  • tehlike 1 hour ago
    It really should be default, but it isn't due to backward compatibility (i assume).
    • poidos 1 hour ago
      It’s a stated [0] goal of the project:

      > SQLite strives to be flexible regarding the datatype of the content that it stores.

      [0]: https://sqlite.org/stricttables.html

      • masklinn 57 minutes ago
        You can be flexible with strict tables, type every column as ANY and you pretty much get back the original behaviour.
        • poidos 49 minutes ago
          Sure, but you lose the representation of the developer’s intention that way. I would be pretty pissed off if I inherited a project and the schema was all ANYs.
          • masklinn 43 minutes ago
            The intent of ANY is obviously that the values be flexible. That’s why it’s there if you need it.
          • drdexebtjl 42 minutes ago
            “I intended this to be an integer but it could really be anything” is not very useful.
            • poidos 37 minutes ago
              Sure it is. If you encounter something that’s not an int, that could be a signal you have a bug in your writers. Or in the source of the data. That’s useful information compared to “oh, I have some ints and some strings, that’s ANY, everything is ok.”
    • tuvix 1 hour ago
      I’m kind of curious why the decision to have implicit casting like this was made in the first place. I can’t think of a single upside other than not having to type out cast(foo as bar)
      • rogerbinns 1 hour ago
        SQLite was originally started as a local database library for use during development for times when the main networked database was not available. It used dbm as the underlying storage mechanism, with the dbm API roughly being string keys with string values. ie all underlying values were actually stored as strings. The SQLite code would automatically do conversions - eg the plus operator would convert the strings to int or float, add them, and generate a stringified number as a result. The vast majority of implementation code did not have to care about types, and very local decisions could be made such as in the addition example.

        TCL was used as a dev wrapper language at the time, and it functioned the same way.

        It was only in mid-2004 that SQLite 3 was released which used its own storage backend, and that allowed for the 5 supported storage types (int64, string, bytes, float, null). It was API compatible (with minor adjustments) with the earlier SQLite 2, so the lack of static typing continued, otherwise everyone would have to rewrite their code. You do get dynamic typing, which hasn't been a problem for the vast majority of SQLite users.

        Do remember that SQLite is competition for fopen, not Oracle / Postgres etc. It is trying to make things as effective as possible in that scenario. If you don't want numbers in your string column, then don't do that!

      • notRobot 1 hour ago
        SQLite docs: The Advantages Of Flexible Typing: https://sqlite.org/flextypegood.html
      • mb7733 48 minutes ago
        It's even worse than implicit casting, if the value can't be cast to the the column's type, it's just inserted without casting. Eg. into an integer column, '10' -> 10 and '1O' -> '1O'
        • rogerbinns 41 minutes ago
          That is documented behaviour - think of it as making a best effort, and not losing the value.

          As of January 2006 you could add CHECK constraints using the TYPEOF function to reject that at the SQL level. And it is your own code - there is no server - doing the insertions. As was common back then, protecting you from your own bugs was not a high priority for APIs!

      • pstuart 1 hour ago
        IIRC, the project started out as TCL code and it carried that vibe through to what it is today.
  • dzonga 18 minutes ago
    the only thing that sucks about SQLite is migrations.
  • Cyberdog 23 minutes ago
    If you're stuck with an older version of SQLite and/or want to enforce order on an existing table without creating a new table with STRICT and then copying all your rows over and/or also want to do things like enforce signedness, int size, or char/varchar length on a field like you can in other DBs, you can use CHECK constraints.

      CREATE TABLE users (
        user_id CHAR(36) NOT NULL PRIMARY KEY CONSTRAINT user_id_length CHECK (LENGTH(user_id) = 36),
        email_address VARCHAR(255) UNIQUE CONSTRAINT email_address_length CHECK (email_address IS NULL OR LENGTH(email_address) < 256),
        role UNSIGNED TINYINT(1) NOT NULL CONSTRAINT role_valid CHECK (role >= 0 AND role <= 9)
      )
    
    Note that the column types here are just to describe to the user what the field should be doing and it's the constraints that actually enforce it. Behind the scenes SQLite still creates two "text (supposedly but whatever)" and one "integer (supposedly but whatever)" columns.

    It's a little frustrating that all this extra cruft is necessary to get the world's most popular RDBMS to take data correctness seriously. I hope that some SQLite fork that behaves more like other RDBMSes when it comes to this stuff catches on some day, but the fact that that hasn't happened yet makes me think that the demand isn't there, somehow, unfortunately.

    https://sqlite.org/lang_createtable.html#ckconst

  • moron4hire 1 hour ago
    Using Entity Framework, this doesn't come up as a particular issue, but I still wish it were strict by default because I expect there could be some performance optimizations made for de/serialization.
  • Eswo 1 hour ago
    really interesting, thanks
  • itsthecourier 1 hour ago
    about the use of ANY, that's perfect for tracking changes on an audit table per field