{"id":13619943,"url":"https://github.com/fukamachi/mito","last_synced_at":"2025-05-16T07:03:32.822Z","repository":{"id":39617489,"uuid":"42090972","full_name":"fukamachi/mito","owner":"fukamachi","description":"An ORM for Common Lisp with migrations, relationships and PostgreSQL support","archived":false,"fork":false,"pushed_at":"2025-05-09T01:40:28.000Z","size":556,"stargazers_count":306,"open_issues_count":40,"forks_count":32,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-05-09T02:39:48.318Z","etag":null,"topics":["common-lisp","orm"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fukamachi.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["fukamachi"]}},"created_at":"2015-09-08T05:01:53.000Z","updated_at":"2025-05-09T01:40:31.000Z","dependencies_parsed_at":"2023-02-14T04:02:03.479Z","dependency_job_id":"a890c3cf-c6b6-4135-ab06-5e137620156a","html_url":"https://github.com/fukamachi/mito","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fukamachi%2Fmito","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fukamachi%2Fmito/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fukamachi%2Fmito/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fukamachi%2Fmito/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fukamachi","download_url":"https://codeload.github.com/fukamachi/mito/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485047,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["common-lisp","orm"],"created_at":"2024-08-01T21:00:50.411Z","updated_at":"2025-05-16T07:03:32.768Z","avatar_url":"https://github.com/fukamachi.png","language":"Common Lisp","funding_links":["https://github.com/sponsors/fukamachi"],"categories":["Common Lisp","Expert Systems"],"sub_categories":[],"readme":"# Mito\n\n[![Build Status](https://github.com/fukamachi/mito/workflows/CI/badge.svg)](https://github.com/fukamachi/mito/actions?query=workflow%3ACI)\n[![Quicklisp dist](http://quickdocs.org/badge/mito.svg)](http://quickdocs.org/mito/)\n\nMito is yet another object relational mapper, and it aims to be a successor of [Integral](https://github.com/fukamachi/integral).\n\n* Supports MySQL, PostgreSQL and SQLite3\n* Adds `id` (serial/uuid primary key), `created_at` and `updated_at` by default like Ruby's ActiveRecord\n* Migrations\n* DB schema versioning\n\n## Warning\n\nThis software is still ALPHA quality. The APIs likely change.\n\nThis software should work fine with MySQL, PostgreSQL and SQLite3 on SBCL/Clozure CL.\n\n## Usage\n\n```common-lisp\n(mito:connect-toplevel :mysql :database-name \"myapp\" :username \"fukamachi\" :password \"c0mon-1isp\")\n;=\u003e #\u003cDBD.MYSQL:\u003cDBD-MYSQL-CONNECTION\u003e {100691BFF3}\u003e\n\n(mito:deftable user ()\n  ((name :col-type (:varchar 64))\n   (email :col-type (or (:varchar 128) :null))))\n;=\u003e #\u003cMITO.DAO.TABLE:DAO-TABLE-CLASS COMMON-LISP-USER::USER\u003e\n\n(mito:table-definition 'user)\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, email VARCHAR(128))\u003e)\n\n(mito:deftable tweet ()\n  ((status :col-type :text)\n   (user :col-type user)))\n;=\u003e #\u003cMITO.DAO.TABLE:DAO-TABLE-CLASS COMMON-LISP-USER::TWEET\u003e\n\n(mito:table-definition 'tweet)\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE tweet (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, status TEXT NOT NULL, user_id BIGINT UNSIGNED NOT NULL, created_at TIMESTAMP, updated_at TIMESTAMP)\u003e)\n```\n\n### Connecting to DB\n\nMito provides the functions `connect-toplevel` and `disconnect-toplevel` to establish and sever a connection to RDBMS.\n\n`connect-toplevel` takes the same arguments as `dbi:connect`: typically the driver-type, the database name to connect, user name and password.\n\n```common-lisp\n(mito:connect-toplevel :mysql :database-name \"myapp\" :username \"fukamachi\" :password \"c0mon-1isp\")\n```\n\n`connect-toplevel` sets `*connection*` to a new connection and returns it.\n\nTo use a connection lexically, just bind it:\n\n```common-lisp\n(let ((mito:*connection* (dbi:connect :sqlite3 :database-name #P\"/tmp/myapp.db\")))\n  (unwind-protect (progn ...)\n    ;; Ensure that the connection is closed.\n    (dbi:disconnect mito:*connection*)))\n```\n\nIn most cases `dbi:connect-cached` is a better option, since it reuses a connection for multiple threads.\n\n```common-lisp\n(let ((mito:*connection* (dbi:connect-cached :sqlite3 :database-name #P\"/tmp/myapp.db\")))\n  (unwind-protect (progn ...)\n    ;; Ensure that the connection is closed.\n    ))\n```\n\nUse `connection-database-name` to get the name of the current connection, or of one named via parameter.\n\nIf you are using [clack](https://github.com/fukamachi/clack) as your webserver, A middleware is provided.\n\n```common-lisp\n(clack:clackup\n  (lack:builder\n    (:mito '(:sqlite3 :database-name #P\"/tmp/myapp.db\"))\n    ...\n    *app*))\n```\n\n#### Connecting To `sqlite3` In Memory\n\nTo connect to a `sqlite3` in memory database without having to save a file you can do:\n\n```common-lisp\n(mito:connect-toplevel :sqlite3\n                       :database-name #P\":memory:\")\n```\n\n### deftable macro\n\nAs Mito's dao table class is defined as a CLOS metaclass, a table class can be defined like this:\n\n```common-lisp\n(defclass user ()\n  ((name :col-type (:varchar 64)\n         :accessor user-name)\n   (email :col-type (or (:varchar 128) :null)\n          :accessor user-email))\n  (:metaclass mito:dao-table-class))\n```\n\n`deftable`'s syntax is the same as that of `cl:defclass`. However, the definition is a little bit redundant.\n\n`mito:deftable` is a thin macro, to allow definion of a table class with less typing.\n\nFor example, the above example can be rewritten, using `deftable` as follows.\n\n```common-lisp\n(mito:deftable user ()\n  ((name :col-type (:varchar 64))\n   (email :col-type (or (:varchar 128) :null))))\n```\n\nIt adds `:metaclass mito:dao-table-class`, and adds default accessors that start with `\u003cclass-name\u003e-` by default, like `defstruct` does.\n\nThe prefix for accessors can be changed with the `:conc-name` class option:\n\n```common-lisp\n(mito:deftable user ()\n  ((name :col-type (:varchar 64))\n   (email :col-type (or (:varchar 128) :null)))\n  (:conc-name my-))\n\n(my-name (make-instance 'user :name \"fukamachi\"))\n;=\u003e \"fukamachi\"\n```\n\nIf `:conc-name` is NIL, default accessors will NOT be defined.\n\n### Class Definitions\n\nIn Mito, a class corresponding to a database table is defined by specifying `(:metaclass mito:dao-table-class)`.\n\n```common-lisp\n(defclass user ()\n  ((name :col-type (:varchar 64)\n         :accessor user-name)\n   (email :col-type (or (:varchar 128) :null)\n          :accessor user-email))\n  (:metaclass mito:dao-table-class))\n```\n\nThe above defines a Common Lisp normal class, except that it allows additional options.\n\n```\n(defclass {class-name} ()\n  ({column-definition}*)\n  (:metaclass mito:dao-table-class)\n  [[class-option]])\n\ncolumn-definition ::= (slot-name [[column-option]])\ncolumn-option ::= {:col-type col-type} |\n                  {:primary-key boolean} |\n                  {:inflate inflation-function} |\n                  {:deflate deflation-function} |\n                  {:references {class-name | (class-name slot-name)}} |\n                  {:ghost boolean}\ncol-type ::= { keyword |\n              (keyword . args) |\n              (or keyword :null) |\n              (or :null keyword) }\nclass-option ::= {:primary-key symbol*} |\n                 {:unique-keys {symbol | (symbol*)}*} |\n                 {:keys {symbol | (symbol*)}*} |\n                 {:table-name table-name} |\n                 {:auto-pk auto-pk-mixin-class-name} |\n                 {:record-timestamps boolean} |\n                 {:conc-name conc-name}\nauto-pk-mixin-class-name ::= {:serial | :uuid}\nconc-name ::= {null | string-designator}\n```\n\nNote: the class automatically adds some slots -- a primary key named `id` if there is no primary key, `created_at` and `updated_at` for recording timestamps. To disable these behaviors, specify `:auto-pk nil` or `:record-timestamps nil` to defclass forms.\n\n```common-lisp\n(mito.class:table-column-slots (find-class 'user))\n;=\u003e (#\u003cMITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS MITO.DAO.MIXIN::ID\u003e\n;    #\u003cMITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS COMMON-LISP-USER::NAME\u003e\n;    #\u003cMITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS COMMON-LISP-USER::EMAIL\u003e\n;    #\u003cMITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS MITO.DAO.MIXIN::CREATED-AT\u003e\n;    #\u003cMITO.DAO.COLUMN:DAO-TABLE-COLUMN-CLASS MITO.DAO.MIXIN::UPDATED-AT\u003e)\n```\n\nThis class inherits `mito:dao-class` implicitly.\n\n```common-lisp\n(find-class 'user)\n;=\u003e #\u003cMITO.DAO.TABLE:DAO-TABLE-CLASS COMMON-LISP-USER::USER\u003e\n\n(c2mop:class-direct-superclasses *)\n;=\u003e (#\u003cSTANDARD-CLASS MITO.DAO.TABLE:DAO-CLASS\u003e)\n```\n\nThis may be useful to define methods that can be applied for many or all table classes.\n\n#### :col-type Options\n\nThe following are valid keywords for :col-type in the `deftable` definition above.\n\n```common-lisp\n:serial\n:bigserial\n:timestamptz\n:integer\n:bytea\n:timestamp\n:bigint\n:unsigned\n:int\n:binary\n:datetime\n```\n\nBesides the above keywords, there are other keywords that are valid, however they are dependent on the RDS and its version.\n\nAn example of this is that `:json` and `:jsonb` work for PostgreSQL but don't work on an old version of MySQL which doesn't support those types.\n\nA complete list of valid `:col-type` options is dependent on the database system. Here's a link for the current Data Types for:\n- [PostgreSQL Data Types](https://www.postgresql.org/docs/current/datatype.html#DATATYPE-TABLE)\n- [MySQL Data Types](https://dev.mysql.com/doc/refman/8.0/en/data-types.html)\n- [SQLite3 Data Types](https://www.sqlite.org/datatype3.html)\n\nThe symbols are not defined directly in the system, rather they are the symbol equivalent of the string which is the name for the data type. Therefore, for any data type name, just preprend a colon to the name `:data-type` in order to use it as a `col-type`.\n\n##### :col-type Definitions with Qualifiers\n\nFor some data types there are qualifiers available.\n\nWhen there is only **one** qualfier in the data type, it can be given like in the following example\n\n```lisp\n(name :col-type (:varchar 64))\n```\n\nHowever, when there is **more than one** qualifier, providing a list of qualifiers **does not currently work**.\n\nA **workaround** that works is giving the whole data type definition, including the qualifier, as a string.\n\nFor example the following will work:\n\n```lisp\n(price :col-type \"numeric(10,2)\")\n```\n\nHowever note that the following examples will **not work**:\n\n```lisp\n(price :col-type (:numeric \"10,2\"))\n(price :col-type (:numeric 10 2))\n```\n\nCommon Lisp does not accept parenthesis and commas as valid variable names, so `:numeric(10,2)` and `:numeric10,2` are obviously invalid.\n\nKeep this in mind in particular when using `NUMERIC`, `DECIMAL`, and spatial data types.\n\n### Generating Table Definitions\n\n```common-lisp\n(mito:table-definition 'user)\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, email VARCHAR(128), created_at TIMESTAMP, updated_at TIMESTAMP)\u003e)\n\n(sxql:yield *)\n;=\u003e \"CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, email VARCHAR(128), created_at TIMESTAMP, updated_at TIMESTAMP)\"\n;   NIL\n```\n\n### Creating DB tables\n\n```common-lisp\n(mapc #'mito:execute-sql (mito:table-definition 'user))\n\n(mito:ensure-table-exists 'user)\n```\n\n### CRUD\n\n```common-lisp\n(defvar me\n  (make-instance 'user :name \"Eitaro Fukamachi\" :email \"e.arrows@gmail.com\"))\n;=\u003e USER\n\n(mito:insert-dao me)\n;-\u003e ;; INSERT INTO `user` (`name`, `email`, `created_at`, `updated_at`) VALUES (?, ?, ?, ?) (\"Eitaro Fukamachi\", \"e.arrows@gmail.com\", \"2016-02-04T19:55:16.365543Z\", \"2016-02-04T19:55:16.365543Z\") [0 rows] | MITO.DAO:INSERT-DAO\n;=\u003e #\u003cUSER {10053C4453}\u003e\n\n;; Same as above\n(mito:create-dao 'user :name \"Eitaro Fukamachi\" :email \"e.arrows@gmail.com\")\n\n;; Getting the primary key value\n(mito:object-id me)\n;=\u003e 1\n\n;; Retrieving from the DB\n(mito:find-dao 'user :id 1)\n;-\u003e ;; SELECT * FROM `user` WHERE (`id` = ?) LIMIT 1 (1) [1 row] | MITO.DB:RETRIEVE-BY-SQL\n;=\u003e #\u003cUSER {10077C6073}\u003e\n\n(mito:retrieve-dao 'user)\n;=\u003e (#\u003cUSER {10077C6073}\u003e)\n\n;; Updating\n(setf (slot-value me 'name) \"nitro_idiot\")\n;=\u003e \"nitro_idiot\"\n\n(mito:save-dao me)\n;-\u003e ;; UPDATE `user` SET `id` = ?, `name` = ?, `email` = ?, `created_at` = ?, `updated_at` = ? WHERE (`id` = ?) (2, \"nitro_idiot\", \"e.arrows@gmail.com\", \"2016-02-04T19:56:11.408927Z\", \"2016-02-04T19:56:19.006020Z\", 2) [0 rows] | MITO.DAO:UPDATE-DAO\n\n;; Deleting\n(mito:delete-dao me)\n;-\u003e ;; DELETE FROM `user` WHERE (`id` = ?) (1) [0 rows] | MITO.DAO:DELETE-DAO\n(mito:delete-by-values 'user :id 1)\n;-\u003e ;; DELETE FROM `user` WHERE (`id` = ?) (1) [0 rows] | MITO.DAO:DELETE-DAO\n\n;; Counting\n(mito:count-dao 'user)\n;-\u003e 1\n```\n\n### Custom queries\nMito is at its core a rather thin wrapper around sxql and cl-dbi for converting sql results to special types and back. Most of the porcelain functions shown above are acutally implemented in just under 200 lines.\n\nGiven a plist which represents the result from the database, you can apply `make-dao-instance` To make it into a `dao-class` automatically doing inflation/deflation.\n\nTo run a custom query, use `retrieve-by-sql` which returns a list of plists.\n```common-lisp\n(mito:retrieve-by-sql\n  (select (:user.*)\n    (from :users)\n    ;; Using a subquery to avoid a join and distinct\n    ;; Make sure you actually test performance before doing this in production\n    (where (:in :user.name\n                (select (:poster)\n                  (from :tweets)\n                  (where (:\u003e :tweets.likes 1000))\n                  (returning :poster))))))\n;=\u003e ((:name \"Shinmera\" :email \"shinmera@tymoon.eu\" :followers 200000)\n;    (:name \"Fukamachi\" :email \"e.arrows@gmail.com\" :followers 100000) ...)\n```\n\nYou can use `select-by-sql` if you want to automatically convert it to a class.\n\n```common-lisp\n(mito:select-by-sql 'user\n  (select (:user.*)\n    (from :users)\n    (where (:in :user.name\n                (select (:poster)\n                  (from :tweets)\n                  (where (:\u003e :tweets.likes 1000))\n                  (returning :poster))))))\n;=\u003e (#\u003cUSER {1003E769E3}\u003e #\u003cUSER {10040637A3}\u003e)\n```\nThe actual definition is basically `mapcar #'make-dao-instance` over the results of `retrieve-by-sql`\n\nFinally `select-dao` provides the highest level API. This is usually what you need.\n```common-lisp\n(mito:select-dao 'user\n  (where (:in :user.name\n              (select (:poster)\n                (from :tweets)\n                (where (:\u003e :tweets.likes 1000))\n                (returning :poster)))))\n;=\u003e (#\u003cUSER {1003E769E3}\u003e #\u003cUSER {10040637A3}\u003e)\n```\n\nIt also provides neat facilities such as an `includes` clause so that you don't have to write out joins by hand (examples below).\n\n### Relationship\n\nTo define a relationship, use `:references` on the slot:\n\n```common-lisp\n(mito:deftable user ()\n  ((id :col-type (:varchar 36)\n       :primary-key t)\n   (name :col-type (:varchar 64))\n   (email :col-type (or (:varchar 128) :null))))\n\n(mito:deftable tweet ()\n  ((status :col-type :text)\n   ;; This slot refers to USER class\n   (user-id :references (user id))))\n\n;; The :col-type of USER-ID column is retrieved from the foreign class.\n(mito:table-definition (find-class 'tweet))\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE tweet (\n;       id BIGSERIAL NOT NULL PRIMARY KEY,\n;       status TEXT NOT NULL,\n;       user_id VARCHAR(36) NOT NULL,\n;       created_at TIMESTAMPTZ,\n;       updated_at TIMESTAMPTZ\n;   )\u003e)\n```\n\nYou can also specify another foreign class at `:col-type` to define a relationship:\n\n```common-lisp\n(mito:deftable tweet ()\n  ((status :col-type :text)\n   ;; This slot refers to USER class\n   (user :col-type user)))\n\n(mito:table-definition (find-class 'tweet))\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE tweet (\n;        id BIGSERIAL NOT NULL PRIMARY KEY,\n;        status TEXT NOT NULL,\n;        user_id VARCHAR(36) NOT NULL,\n;        created_at TIMESTAMP,\n;        updated_at TIMESTAMP\n;    )\u003e)\n\n;; You can specify :USER arg, instead of :USER-ID.\n(defvar *user* (mito:create-dao 'user :name \"Eitaro Fukamachi\"))\n(mito:create-dao 'tweet :user *user*)\n\n(mito:find-dao 'tweet :user *user*)\n```\n\nThe latter example allows you to create/retrieve `TWEET` by a `USER` object, not a `USER-ID`.\n\nMito doesn't add foreign key constraints for referring tables, since I'm not sure it's still handful while using with ORMs.\n\n### Inflation/Deflation\n\nInflation/Deflation is a function to convert values between Mito and RDBMS.\n\n```common-lisp\n(mito:deftable user-report ()\n  ((title :col-type (:varchar 100))\n   (body :col-type :text\n         :initform \"\")\n   (reported-at :col-type :timestamp\n                :initform (local-time:now)\n                :inflate #'local-time:universal-to-timestamp\n                :deflate #'local-time:timestamp-to-universal))\n  (:conc-name report-))\n```\n\n### Eager loading\n\nOne of the pains in the neck to use ORMs is the \"N+1 query\" problem.\n\n```common-lisp\n;; BAD EXAMPLE\n\n(use-package '(:mito :sxql))\n\n(defvar *tweets-contain-japan*\n  (select-dao 'tweet\n    (where (:like :status \"%Japan%\"))))\n\n;; Getting names of tweeted users.\n(mapcar (lambda (tweet)\n          (user-name (tweet-user tweet)))\n        *tweets-contain-japan*)\n```\n\nThis example sends a query to retrieve a user, like \"SELECT * FROM user WHERE id = ?\" for each iteration.\n\nTo prevent this performance issue, add `includes` to the above query, which sends only a single WHERE IN query instead of N queries:\n\n```common-lisp\n;; GOOD EXAMPLE with eager loading\n\n(use-package '(:mito :sxql))\n\n(defvar *tweets-contain-japan*\n  (select-dao 'tweet\n    (includes 'user)\n    (where (:like :status \"%Japan%\"))))\n;-\u003e ;; SELECT * FROM `tweet` WHERE (`status` LIKE ?) (\"%Japan%\") [3 row] | MITO.DB:RETRIEVE-BY-SQL\n;-\u003e ;; SELECT * FROM `user` WHERE (`id` IN (?, ?, ?)) (1, 3, 12) [3 row] | MITO.DB:RETRIEVE-BY-SQL\n;=\u003e (#\u003cTWEET {1003513EC3}\u003e #\u003cTWEET {1007BABEF3}\u003e #\u003cTWEET {1007BB9D63}\u003e)\n\n;; No additional SQLs will be executed.\n(tweet-user (first *))\n;=\u003e #\u003cUSER {100361E813}\u003e\n```\n\n### Migrations\n\n```common-lisp\n(ensure-table-exists 'user)\n;-\u003e ;; CREATE TABLE IF NOT EXISTS \"user\" (\n;       \"id\" BIGSERIAL NOT NULL PRIMARY KEY,\n;       \"name\" VARCHAR(64) NOT NULL,\n;       \"email\" VARCHAR(128),\n;       \"created_at\" TIMESTAMP,\n;       \"updated_at\" TIMESTAMP\n;   ) () [0 rows] | MITO.DAO:ENSURE-TABLE-EXISTS\n\n;; No changes\n(mito:migration-expressions 'user)\n;=\u003e NIL\n\n(mito:deftable user ()\n  ((name :col-type (:varchar 64))\n   (email :col-type (:varchar 128)))\n  (:unique-keys email))\n\n(mito:migration-expressions 'user)\n;=\u003e (#\u003cSXQL-STATEMENT: ALTER TABLE user ALTER COLUMN email TYPE character varying(128), ALTER COLUMN email SET NOT NULL\u003e\n;    #\u003cSXQL-STATEMENT: CREATE UNIQUE INDEX unique_user_email ON user (email)\u003e)\n\n(mito:migrate-table 'user)\n;-\u003e ;; ALTER TABLE \"user\" ALTER COLUMN \"email\" TYPE character varying(128), ALTER COLUMN \"email\" SET NOT NULL () [0 rows] | MITO.MIGRATION.TABLE:MIGRATE-TABLE\n;   ;; CREATE UNIQUE INDEX \"unique_user_email\" ON \"user\" (\"email\") () [0 rows] | MITO.MIGRATION.TABLE:MIGRATE-TABLE\n;-\u003e (#\u003cSXQL-STATEMENT: ALTER TABLE user ALTER COLUMN email TYPE character varying(128), ALTER COLUMN email SET NOT NULL\u003e\n;    #\u003cSXQL-STATEMENT: CREATE UNIQUE INDEX unique_user_email ON user (email)\u003e)\n```\n\nSQLite3 migration creates temporary tables with pre-migration data. To delete them after migration is complete set\n`mito:*migration-keep-temp-tables*` to `nil`. It has no effect on other drivers.\n\n#### Auto migrations\n\nIf `mito:*auto-migration-mode*` is set to `t`, and you are connected to a database, Mito will run migrations after\neach change to model definitions.\n\n### Schema versioning\n\n```\n$ ros install mito\n$ mito\nUsage: mito command [option...]\n\nCommands:\n    generate-migrations\n    migrate\n    migration-status\n\nOptions:\n    -t, --type DRIVER-TYPE          DBI driver type (one of \"mysql\", \"postgres\" or \"sqlite3\")\n    -d, --database DATABASE-NAME    Database name to use\n    -u, --username USERNAME         Username for RDBMS\n    -p, --password PASSWORD         Password for RDBMS\n    -s, --system SYSTEM             ASDF system to load (several -s's allowed)\n    -D, --directory DIRECTORY       Directory path to keep migration SQL files (default: \"/Users/nitro_idiot/Programs/lib/mito/db/\")\n    --dry-run                       List SQL expressions to migrate\n    -f, --force                     Create a new empty migration file even when it's unnecessary.\n```\n\n#### Example\n\n```\nmito --database postgres --username fukamachi --pasword c0mmon-l1sp\n```\n\n### Inheritance and Mixin\n\nA subclass of DAO-CLASS is allowed to be inherited. This may be useful when you need classes that have similar columns:\n\n```common-lisp\n(mito:deftable user ()\n  ((name :col-type (:varchar 64))\n   (email :col-type (:varchar 128)))\n  (:unique-keys email))\n\n(mito:deftable temporary-user (user)\n  ((registered-at :col-type :timestamp)))\n\n(mito:table-definition 'temporary-user)\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE temporary_user (\n;        id BIGSERIAL NOT NULL PRIMARY KEY,\n;        name VARCHAR(64) NOT NULL,\n;        email VARCHAR(128) NOT NULL,\n;        registered_at TIMESTAMP NOT NULL,\n;        created_at TIMESTAMP,\n;        updated_at TIMESTAMP,\n;        UNIQUE (email)\n;    )\u003e)\n```\n\nIf you need a 'template' for tables, not related to any specific database table, you can use `DAO-TABLE-MIXIN`:\n\n```common-lisp\n(defclass has-email ()\n  ((email :col-type (:varchar 128)\n          :accessor object-email))\n  (:metaclass mito:dao-table-mixin)\n  (:unique-keys email))\n;=\u003e #\u003cMITO.DAO.MIXIN:DAO-TABLE-MIXIN COMMON-LISP-USER::HAS-EMAIL\u003e\n\n(mito:deftable user (has-email)\n  ((name :col-type (:varchar 64))))\n;=\u003e #\u003cMITO.DAO.TABLE:DAO-TABLE-CLASS COMMON-LISP-USER::USER\u003e\n\n(mito:table-definition 'user)\n;=\u003e (#\u003cSXQL-STATEMENT: CREATE TABLE user (\n;       id BIGSERIAL NOT NULL PRIMARY KEY,\n;       name VARCHAR(64) NOT NULL,\n;       email VARCHAR(128) NOT NULL,\n;       created_at TIMESTAMP,\n;       updated_at TIMESTAMP,\n;       UNIQUE (email)\n;   )\u003e)\n```\n\nExamples of inheritance can be found here:\n\n* [mito-attachment](https://github.com/fukamachi/mito-attachment)\n* [mito-auth](https://github.com/fukamachi/mito-auth)\n\n### Triggers\n\nSince `insert-dao`, `update-dao` and `delete-dao` are defined as generic functions, you can define `:before`, `:after` or `:around` methods on those.\n\n```common-lisp\n(defmethod mito:insert-dao :before ((object user))\n  (format t \"~\u0026Adding ~S...~%\" (user-name object)))\n\n(mito:create-dao 'user :name \"Eitaro Fukamachi\" :email \"e.arrows@gmail.com\")\n;-\u003e Adding \"Eitaro Fukamachi\"...\n;   ;; INSERT INTO \"user\" (\"name\", \"email\", \"created_at\", \"updated_at\") VALUES (?, ?, ?, ?) (\"Eitaro Fukamachi\", \"e.arrows@gmail.com\", \"2016-02-16 21:13:47\", \"2016-02-16 21:13:47\") [0 rows] | MITO.DAO:INSERT-DAO\n;=\u003e #\u003cUSER {100835FB33}\u003e\n```\n\n### Iteration (Experimental)\n\n`do-select` is a macro to iterate over results from SELECT one by one. It's the same as `cl:loop`, but it uses CURSOR for PostgreSQL, which can reduce memory usage since it won't load whole results on memory.\n\n```common-lisp\n(do-select (dao (select-dao 'user\n                  (where (:\u003c \"2024-07-01\" :created_at))))\n  ;; Can be a more complex condition\n  (when (equal (user-name dao) \"Eitaro\")\n    (return dao)))\n\n;; Same but without using CURSOR\n(loop for dao in (select-dao 'user\n                   (where (:\u003c \"2024-07-01\" :created_at)))\n      when (equal (user-name dao) \"Eitaro\")\n      do (return dao))\n```\n\nThe query form must be one of `select-dao`, `retrieve-dao`, or `select-by-sql`.\n\n## Installation\n\n```common-lisp\n(ql:quickload :mito)\n```\n\nOr, with Roswell:\n\n```\nros install mito\n```\n\nIf you build a binary, reference a DB driver in your dependencies:\n\n    :dbd-sqlite3 :dbd-mysql :dbd-postgres\n\n\n## Mito Extensions and Plugins\n\n* [mito-attachment](https://github.com/fukamachi/mito-attachment)\n* [mito-auth](https://github.com/fukamachi/mito-auth)\n\n## See Also\n\n* [CL-DBI](https://github.com/fukamachi/cl-dbi)\n* [SxQL](https://github.com/fukamachi/sxql)\n\n## Author\n\n* Eitaro Fukamachi (e.arrows@gmail.com)\n\n## Copyright\n\nCopyright (c) 2015 Eitaro Fukamachi (e.arrows@gmail.com)\n\n## License\n\nLicensed under the BSD 3-Clause License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffukamachi%2Fmito","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffukamachi%2Fmito","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffukamachi%2Fmito/lists"}