{
    "archive_path": "archive/1783268988.504487",
    "base_url": "wozniak.ca/blog/2014/08/03/1/index.html",
    "basename": "index.html",
    "bookmarked_date": "2026-07-05 16:29",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/wozniak.ca/blog/2014/08/03/1/index.html",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=wozniak.ca",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "wozniak.ca",
    "downloaded_at": "2026-07-05T16:29:53.937987+00:00",
    "downloaded_datestr": "2026-07-05 16:29",
    "extension": "html",
    "hash": "JZB2VNJJ4V45EAAED30N",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T16:30:57.096765+00:00",
                "index_texts": null,
                "output": "ArchiveError: Failed to find \"content-location\" URL header in Archive.org response.",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:54.826376+00:00",
                "status": "failed"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2026-07-05T16:30:26.688230+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:17.227768+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=wozniak.ca"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T16:29:58.523061+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:29:54.408887+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T16:29:58.823275+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:29:58.565158+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2026-07-05T16:30:46.278745+00:00",
                "index_texts": [
                    "What ORMs have taught me: just learn SQL (/resources/favicon.png) (/resources/site.css) (Curried Lambda) (/blog/atom.xml) (Curried Lambda) (/blog/rss.xml)  (/) Main  (/blog) Blog ((/blog/atom.xml) (Atom feed)  )    What ORMs have taught me: just learn SQL I\u2019ve come to the conclusion that, for me, ORMs are more detriment than\nbenefit. In short, they can be used to nicely augment working with SQL\nin a program, but they should not replace it.  Some background: For the past 30 months I\u2019ve been working with code\nthat has to interface with Postgres and to some extent, SQLite. Most\nof that has been with (http://sqlalchemy.org/) SQLAlchemy (which I quite like) and (http://hibernate.org/) Hibernate (which I don\u2019t). I\u2019ve worked with existing code and data models, as\nwell as designing my own. Most of the data is event-based storage\n(\u201ctimelines\u201d) with a heavy emphasis on creating reports.  Much has been written about the Object/Relational Impedance\nMismatch. It\u2019s hard to appreciate it until you live it. Neward, in his (http://blogs.tedneward.com/post/the-vietnam-of-computer-science/) well known essay , lays out many cogent reasons why ORMs turn into\nquagmires. In my experience, I\u2019ve had to deal directly with a fair\nnumber of them: entity identity issues , dual-schema problem , data\nretrieval mechanism concern , and the partial-object problem . I want to\ntalk briefly about my experiences with these issues and add one of my\nown.  Partial objects, attribute creep, and foreign keys Perhaps the most subversive issue I\u2019ve had with ORMs is \u201cattribute\ncreep\u201d or \u201cwide tables\u201d, that is, tables that just keep accruing\nattributes. As much as I\u2019d like to avoid it, sometimes it becomes\nnecessary (although things like (http://www.postgresql.org/docs/9.3/interactive/hstore.html) Postgres\u2019 hstore can help). For\nexample, a client may be providing you with lots of data that they\nwant attached to reports based on various business logic. Furthermore,\nyou don\u2019t have much insight into this data; you\u2019re just schlepping it\naround.  This in and of itself isn\u2019t a terrible thing in a database. It becomes\na real pain point with an ORM. Specifically, the problem starts to\nshow up in any query that uses the entity directly to create the\nquery. You may have a Hibernate query like so early on in the project.  query(Foo.class).add(Restriction.eq(\"x\", value))  This may be fine when Foo has five attributes, but becomes a data fire\nhose when it has a hundred. This is the equivalent of using SELECT\n* , which is usually saying more than what is intended. ORMs, however,\nencourage this use and often make writing precise projections as\ntedious as they are in SQL. (I have optimized such queries by adding\nthe appropriate projection and reduced the run time from minutes to\nseconds; all the time was spent translating the database row into a\nJava object.)  Which leads to another bad experience: the pernicious use of foreign\nkeys. In the ORMs I\u2019ve used, links between classes are represented in\nthe data model as foreign keys which, if not configured carefully,\nresult in a large number of joins when retrieving the object. (A\nrecent count of one such table in my work resulted in over 600\nattributes and 14 joins to access a single object, using the preferred\nquery methodology.)  Attribute creep and excessive use of foreign keys shows me is that in\norder to use ORMs effectively, you still need to know SQL. My\ncontention with ORMs is that, if you need to know SQL, just use SQL\nsince it prevents the need to know how non-SQL gets translated to SQL.    Data retrieval Knowing how to write SQL becomes even more important when you attempt\nto actually write queries using an ORM. This is especially important\nwhen efficiency is a concern.  From what I\u2019ve seen, unless you have a really simple data model (that\nis, you never do joins), you will be bending over backwards to figure\nout how to get an ORM to generate SQL that runs efficiently. Most of\nthe time, it\u2019s more obfuscated than actual SQL.  And if you elect to keep the query simple, you end up doing a lot of\nwork in the code that could be done in the database faster. (https://en.wikipedia.org/wiki/Window_function_%2528SQL%2529#Window_function) Window\nfunctions are relatively advanced SQL that is painful to write with\nORMs. Not writing them into the query likely means you will be\ntransferring a lot of extra data from the database to your\napplication.  In these cases, I\u2019ve elected to write queries using a templating\nsystem and describe the tables using the ORM. I get the convenience of\nan application level description of the table with direct use of\nSQL. It\u2019s a lot less trouble than anything else I\u2019ve used so far.    Dual schema dangers This one seems to be one of those unavoidable redundancies.  If you\ntry to get rid of it, you only make more problems or add excessive\ncomplexity.  The problem is that you end up having a data definition in two places:\nthe database and your application.  If you keep the definition\nentirely in the application, you end up having to write the SQL Data\nDefinition Language (DDL) with the ORM code, which is the same\ncomplication as writing advanced queries in the ORM.  If you keep it\nin the database, you will probably want a representation in the\napplication for convenience and to prevent too much \u201cstring typing\u201d.  I much prefer to keep the data definition in the database and read it\ninto the application.  It doesn\u2019t solve the problem, but it makes it\nmore manageable.  I\u2019ve found that reflection techniques to get the\ndata definition are not worth it and I succumb to managing the\nredundancy of data definitons in two places.  But the damn migration issue is a real kick in the teeth: changing the\nmodel is no big deal in the application, but a real pain in the\ndatabase.  After all, databases are persistent whereas application\ndata is not.  ORMs simply get in the way here because they don\u2019t help\nmanage data migration at all.  I work on the principle that the\ndatabase\u2019s data definitions aren\u2019t things you should manipulate in the\napplication.  Instead, manipulate the results of queries.  That is,\nthe queries are your API to the database.  So instead of thinking\nabout objects, I think about functions with return types.  Thus, one is forced to ask, should you use an ORM for anything but\nconvenience in making queries?    Identities Dealing with entity identities is one of those things that you have to\nkeep in mind at all times when working with ORMs, forcing you to write\nfor two systems while only have the expressivity of one.  When you have foreign keys, you refer to related identities with an\nidentifier. In your application, \u201cidentifier\u201d takes on various\nmeanings, but usually it\u2019s the memory location (a pointer). In the\ndatabase, it\u2019s the state of the object itself. These two things don\u2019t\nreally get along because you can really only use database identifiers\nin the database (the ultimate destination of the data you\u2019re working\nwith).  What this results in is having to manipulate the ORM to get a database\nidentifier by manually flushing the cache or doing a partial commit to\nget the actual database identifier.  I can\u2019t even call this a leaky abstraction because the work \u201cleak\u201d\nimplies small amounts of the contents escaping relative to the source.    Transactions Something that Neward alludes to is the need for developers to handle\ntransactions. Transactions are dynamically scoped, which is a powerful\nbut mostly neglected concept in programming languages due to the\nconfusion they cause if overused.  This leads to a lot of boilerplate\ncode with exception handlers and a careful consideration of where\ntransaction boundaries should occur.  It also makes you pass session\nobjects around to any function/method that might have to communicate\nwith the database.  The concept of a transaction translates poorly to applications due to\ntheir reliance on context based on time. As mentioned, dynamic scoping\nis one way to use this in a program, but it is at odds with lexical\nscoping, the dominant paradigm. Thus, you must take great care to know\nabout the \u201cwhen\u201d of a transaction when writing code that works with\ndatabases and can make modularity tricky (\u201cHere\u2019s a useful function\nthat will only work in certain contexts\u201d).  Where do I see myself going?  At this point, I\u2019m starting to question the wisdom behind the outright\nrejection of (http://c2.com/cgi/wiki?StoredProcedures) stored procedures .  It sounds (http://c2.com/cgi/wiki?StoredProceduresAreEvil) heretical , but it may work\nfor my use cases.  (And hey, with the advent of \u201cdevops\u201d, the divide\nbetween the developer and the database administrator is basically\nnon-existent.)  I\u2019ve found myself thinking about the database as just another data\ntype that has an API: the queries.  The queries return values of some\ntype, which are represented as some object in the program. By moving\naway from thinking of the objects in my application as something to be\nstored in a database (the raison d\u2019\u00eatre for ORMs) and instead thinking\nof the database as a (large and complex) data type, I\u2019ve found working\nwith a database from an application to be much simpler. And wondering\nwhy I didn\u2019t see it earlier.  (It should be made clear that I am not claiming this is how all\napplications should deal with a database.  All I am saying is that\nthis fits my use case based on the data I am working with.)  Regardless of whether I find that stored procedures aren\u2019t actually\nthat evil or whether I keep using templated SQL, I do know one thing:\nI won\u2019t fall into the \u201cORMs make it easy\u201d trap. They are an acceptable\nway to represent a data definition, but a poor way to write queries\nand a bad way to store object state. If you\u2019re using an RDBMS, bite\nthe bullet and learn SQL.     August 3, 2014  (mailto:comment@wozniak.ca) comment@wozniak.ca  Generated on 2022-01-02    "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:46.265967+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2026-07-05T16:30:54.751399+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:48.119450+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2026-07-05T16:30:46.227830+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:43.317741+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmpgqkm42nd",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-07-05T16:30:31.057030+00:00",
                "index_texts": [
                    "I\u2019ve come to the conclusion that, for me, ORMs are more detriment than\nbenefit. In short, they can be used to nicely augment working with SQL\nin a program, but they should not replace it.\n\n\n\nSome background: For the past 30 months I\u2019ve been working with code\nthat has to interface with Postgres and to some extent, SQLite. Most\nof that has been with SQLAlchemy (which I quite like) and Hibernate\n(which I don\u2019t). I\u2019ve worked with existing code and data models, as\nwell as designing my own. Most of the data is event-based storage\n(\u201ctimelines\u201d) with a heavy emphasis on creating reports.\n\n\n\nMuch has been written about the Object/Relational Impedance\nMismatch. It\u2019s hard to appreciate it until you live it. Neward, in his\nwell known essay, lays out many cogent reasons why ORMs turn into\nquagmires. In my experience, I\u2019ve had to deal directly with a fair\nnumber of them: entity identity issues, dual-schema problem, data\nretrieval mechanism concern, and the partial-object problem. I want to\ntalk briefly about my experiences with these issues and add one of my\nown.\n\n\n\nPartial objects, attribute creep, and foreign keys\n\n\nPerhaps the most subversive issue I\u2019ve had with ORMs is \u201cattribute\ncreep\u201d or \u201cwide tables\u201d, that is, tables that just keep accruing\nattributes. As much as I\u2019d like to avoid it, sometimes it becomes\nnecessary (although things like Postgres\u2019 hstore can help). For\nexample, a client may be providing you with lots of data that they\nwant attached to reports based on various business logic. Furthermore,\nyou don\u2019t have much insight into this data; you\u2019re just schlepping it\naround.\n\n\n\nThis in and of itself isn\u2019t a terrible thing in a database. It becomes\na real pain point with an ORM. Specifically, the problem starts to\nshow up in any query that uses the entity directly to create the\nquery. You may have a Hibernate query like so early on in the project.\n\n\nquery(Foo.class).add(Restriction.eq(\"x\", value))\n\n\n\nThis may be fine when Foo has five attributes, but becomes a data fire\nhose when it has a hundred. This is the equivalent of using SELECT\n*, which is usually saying more than what is intended. ORMs, however,\nencourage this use and often make writing precise projections as\ntedious as they are in SQL. (I have optimized such queries by adding\nthe appropriate projection and reduced the run time from minutes to\nseconds; all the time was spent translating the database row into a\nJava object.)\n\n\n\nWhich leads to another bad experience: the pernicious use of foreign\nkeys. In the ORMs I\u2019ve used, links between classes are represented in\nthe data model as foreign keys which, if not configured carefully,\nresult in a large number of joins when retrieving the object. (A\nrecent count of one such table in my work resulted in over 600\nattributes and 14 joins to access a single object, using the preferred\nquery methodology.)\n\n\n\nAttribute creep and excessive use of foreign keys shows me is that in\norder to use ORMs effectively, you still need to know SQL. My\ncontention with ORMs is that, if you need to know SQL, just use SQL\nsince it prevents the need to know how non-SQL gets translated to SQL.\n\n\n\n\n\nData retrieval\n\n\nKnowing how to write SQL becomes even more important when you attempt\nto actually write queries using an ORM. This is especially important\nwhen efficiency is a concern.\n\n\n\nFrom what I\u2019ve seen, unless you have a really simple data model (that\nis, you never do joins), you will be bending over backwards to figure\nout how to get an ORM to generate SQL that runs efficiently. Most of\nthe time, it\u2019s more obfuscated than actual SQL.\n\n\n\nAnd if you elect to keep the query simple, you end up doing a lot of\nwork in the code that could be done in the database faster. Window\nfunctions are relatively advanced SQL that is painful to write with\nORMs. Not writing them into the query likely means you will be\ntransferring a lot of extra data from the database to your\napplication.\n\n\n\nIn these cases, I\u2019ve elected to write queries using a templating\nsystem and describe the tables using the ORM. I get the convenience of\nan application level description of the table with direct use of\nSQL. It\u2019s a lot less trouble than anything else I\u2019ve used so far.\n\n\n\n\n\nDual schema dangers\n\n\nThis one seems to be one of those unavoidable redundancies.  If you\ntry to get rid of it, you only make more problems or add excessive\ncomplexity.\n\n\n\nThe problem is that you end up having a data definition in two places:\nthe database and your application.  If you keep the definition\nentirely in the application, you end up having to write the SQL Data\nDefinition Language (DDL) with the ORM code, which is the same\ncomplication as writing advanced queries in the ORM.  If you keep it\nin the database, you will probably want a representation in the\napplication for convenience and to prevent too much \u201cstring typing\u201d.\n\n\n\nI much prefer to keep the data definition in the database and read it\ninto the application.  It doesn\u2019t solve the problem, but it makes it\nmore manageable.  I\u2019ve found that reflection techniques to get the\ndata definition are not worth it and I succumb to managing the\nredundancy of data definitons in two places.\n\n\n\nBut the damn migration issue is a real kick in the teeth: changing the\nmodel is no big deal in the application, but a real pain in the\ndatabase.  After all, databases are persistent whereas application\ndata is not.  ORMs simply get in the way here because they don\u2019t help\nmanage data migration at all.  I work on the principle that the\ndatabase\u2019s data definitions aren\u2019t things you should manipulate in the\napplication.  Instead, manipulate the results of queries.  That is,\nthe queries are your API to the database.  So instead of thinking\nabout objects, I think about functions with return types.\n\n\n\nThus, one is forced to ask, should you use an ORM for anything but\nconvenience in making queries?\n\n\n\n\n\nIdentities\n\n\nDealing with entity identities is one of those things that you have to\nkeep in mind at all times when working with ORMs, forcing you to write\nfor two systems while only have the expressivity of one.\n\n\n\nWhen you have foreign keys, you refer to related identities with an\nidentifier. In your application, \u201cidentifier\u201d takes on various\nmeanings, but usually it\u2019s the memory location (a pointer). In the\ndatabase, it\u2019s the state of the object itself. These two things don\u2019t\nreally get along because you can really only use database identifiers\nin the database (the ultimate destination of the data you\u2019re working\nwith).\n\n\n\nWhat this results in is having to manipulate the ORM to get a database\nidentifier by manually flushing the cache or doing a partial commit to\nget the actual database identifier.\n\n\n\nI can\u2019t even call this a leaky abstraction because the work \u201cleak\u201d\nimplies small amounts of the contents escaping relative to the source.\n\n\n\n\n\nTransactions\n\n\nSomething that Neward alludes to is the need for developers to handle\ntransactions. Transactions are dynamically scoped, which is a powerful\nbut mostly neglected concept in programming languages due to the\nconfusion they cause if overused.  This leads to a lot of boilerplate\ncode with exception handlers and a careful consideration of where\ntransaction boundaries should occur.  It also makes you pass session\nobjects around to any function/method that might have to communicate\nwith the database.\n\n\n\nThe concept of a transaction translates poorly to applications due to\ntheir reliance on context based on time. As mentioned, dynamic scoping\nis one way to use this in a program, but it is at odds with lexical\nscoping, the dominant paradigm. Thus, you must take great care to know\nabout the \u201cwhen\u201d of a transaction when writing code that works with\ndatabases and can make modularity tricky (\u201cHere\u2019s a useful function\nthat will only work in certain contexts\u201d).\n\n\n\nWhere do I see myself going?\n\n\n\nAt this point, I\u2019m starting to question the wisdom behind the outright\nrejection of stored procedures.  It sounds heretical, but it may work\nfor my use cases.  (And hey, with the advent of \u201cdevops\u201d, the divide\nbetween the developer and the database administrator is basically\nnon-existent.)\n\n\n\nI\u2019ve found myself thinking about the database as just another data\ntype that has an API: the queries.  The queries return values of some\ntype, which are represented as some object in the program. By moving\naway from thinking of the objects in my application as something to be\nstored in a database (the raison d\u2019\u00eatre for ORMs) and instead thinking\nof the database as a (large and complex) data type, I\u2019ve found working\nwith a database from an application to be much simpler. And wondering\nwhy I didn\u2019t see it earlier.\n\n\n\n(It should be made clear that I am not claiming this is how all\napplications should deal with a database.  All I am saying is that\nthis fits my use case based on the data I am working with.)\n\n\n\nRegardless of whether I find that stored procedures aren\u2019t actually\nthat evil or whether I keep using templated SQL, I do know one thing:\nI won\u2019t fall into the \u201cORMs make it easy\u201d trap. They are an acceptable\nway to represent a data definition, but a poor way to write queries\nand a bad way to store object state. If you\u2019re using an RDBMS, bite\nthe bullet and learn SQL."
                ],
                "output": "readability/",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:27.467720+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://wozniak.ca/blog/2014/08/03/1/index.html"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T16:30:26.782323+00:00",
                "index_texts": null,
                "output": "What ORMs have taught me: just learn SQL",
                "pwd": "/data/archive/1783268988.504487",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T16:30:26.770954+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "ArchiveError: Failed to find \"content-location\" URL header in Archive.org response.",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "What ORMs have taught me: just learn SQL",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1783268988.504487",
    "newest_archive_date": "2026-07-05T16:30:54.826376+00:00",
    "num_failures": 1,
    "num_outputs": 8,
    "oldest_archive_date": "2026-07-05T16:29:54.408887+00:00",
    "path": "/blog/2014/08/03/1/index.html",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01KWSHTNMF24871642015H0G2D",
    "snapshot_id": "c557bd84-19d0-425f-aa64-43420b10404d",
    "sources": [
        "/data/sources/1783268987-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1783268988.504487",
    "title": "What ORMs have taught me: just learn SQL",
    "url": "https://wozniak.ca/blog/2014/08/03/1/index.html"
}