{"id":13503154,"url":"https://github.com/antoyo/tql","last_synced_at":"2025-04-04T08:05:40.980Z","repository":{"id":40462042,"uuid":"65669189","full_name":"antoyo/tql","owner":"antoyo","description":"TQL is a compile-time Rust ORM","archived":false,"fork":false,"pushed_at":"2020-06-02T18:48:10.000Z","size":763,"stargazers_count":389,"open_issues_count":26,"forks_count":16,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-28T07:04:38.148Z","etag":null,"topics":["hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antoyo.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"antoyo","patreon":"antoyo"}},"created_at":"2016-08-14T14:05:28.000Z","updated_at":"2024-12-20T12:29:02.000Z","dependencies_parsed_at":"2022-08-25T05:00:41.347Z","dependency_job_id":null,"html_url":"https://github.com/antoyo/tql","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoyo%2Ftql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoyo%2Ftql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoyo%2Ftql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoyo%2Ftql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antoyo","download_url":"https://codeload.github.com/antoyo/tql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247141903,"owners_count":20890651,"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":["hacktoberfest"],"created_at":"2024-07-31T22:02:39.336Z","updated_at":"2025-04-04T08:05:40.941Z","avatar_url":"https://github.com/antoyo.png","language":"Rust","funding_links":["https://github.com/sponsors/antoyo","https://patreon.com/antoyo","https://www.patreon.com/antoyo"],"categories":["Rust"],"sub_categories":[],"readme":"= TQL\n:source-highlighter: pygments\n\nCompile-time ORM, inspired by Django ORM, written in Rust.\nTql is implemented as a procedural macro and even works on the stable version of Rust (https://github.com/antoyo/tql/tree/master/examples/todo-stable[look at this example to see how to use tql on stable]).\n\n*This library is in alpha stage: it has not been thoroughly tested and its API may change at any time.*\n\nimage:https://img.shields.io/travis/antoyo/tql/master.svg[link=\"https://travis-ci.org/antoyo/tql\"]\nimage:https://img.shields.io/crates/l/tql.svg[link=\"LICENSE\"]\nimage:https://img.shields.io/gitter/room/tql-rs/Lobby.svg[link=\"https://gitter.im/tql-rs/Lobby\"]\nimage:https://img.shields.io/badge/Donate-Patreon-orange.svg[link=\"https://www.patreon.com/antoyo\"]\n\n== Requirements\n\nCurrently, `tql` only supports the `PostgreSQL` and `SQLite` databases (more databases will be supported in the future).\nSo, you need to install `PostgreSQL` (and/or `libsqlite3-sys`) in order to use this crate.\n\n== Usage\n\nFirst, add this to you `Cargo.toml`:\n\n[source,toml]\n----\n[dependencies]\nchrono = \"^0.4.0\"\ntql_macros = \"0.1\"\n\n[dependencies.tql]\nfeatures = [\"chrono\", \"pg\"]\nversion = \"0.1\"\n\n[dependencies.postgres]\nfeatures = [\"with-chrono\"]\nversion = \"^0.15.1\"\n----\n\n(You can remove the `chrono` stuff if you don't want to use the date and time types in your model.)\n\nNext, add this to your crate:\n\n[source,rust]\n----\n#![feature(proc_macro_hygiene)]\n\nextern crate chrono;\nextern crate postgres;\nextern crate tql;\n#[macro_use]\nextern crate tql_macros;\n\nuse postgres::{Connection, TlsMode};\nuse tql::PrimaryKey;\nuse tql_macros::sql;\n----\n\nThen, create your model:\n\n[source,rust]\n----\nuse chrono::DateTime;\nuse chrono::offset::Utc;\n\n#[derive(SqlTable)]\nstruct Model {\n    id: PrimaryKey,\n    text: String,\n    date_added: DateTime\u003cUtc\u003e,\n    // …\n}\n----\n\nNext, create an accessor for your connection:\n\n[source,rust]\n----\nfn get_connection() -\u003e Connection {\n    Connection::connect(\"postgres://test:test@localhost/database\", TlsMode::None).unwrap()\n}\n----\n\nFinally, we can use the `sql!` macro to execute an SQL query:\n\n[source,rust]\n----\nfn main() {\n    let connection = get_connection();\n\n    // We first create the table.\n    // (You might not want to execute this query every time.)\n    let _ = sql!(Model.create());\n\n    // Insert a row in the table.\n    let text = String::new();\n    let id = sql!(Model.insert(text = text, date_added = Utc::now())).unwrap();\n\n    // Update a row.\n    let result = sql!(Model.get(id).update(text = \"new-text\"));\n\n    // Delete a row.\n    let result = sql!(Model.get(id).delete());\n\n    // Query some rows from the table:\n    // get the last 10 rows sorted by date_added descending.\n    let items = sql!(Model.sort(-date_added)[..10]);\n}\n----\n\nThe `sql!()` macro uses the identifier `connection` by default.\n\nLook at the https://github.com/antoyo/tql#syntax-table[following table] to see more examples.\n\n== Usage with SQLite\n\nFirst, change the `postgres` dependency to this one:\n\n[source,toml]\n----\nrusqlite = \"^0.13.0\"\n----\n\nThen, change the features of the `tql` dependency:\n\n[source,toml]\n----\n[dependencies.tql]\nfeatures = [\"sqlite\"]\nversion = \"0.1\"\n----\n\nIn the Rust code, the connection needs to come from `rusqlite` now:\n\n[source,rust]\n----\nuse rusqlite::Connection;\n\nfn get_connection() -\u003e Connection {\n    Connection::open(\"database.db\").unwrap()\n}\n----\n\nAnd the rest is the same.\n\n== Using on stable Rust\n\nIf you want to use `tql` on stable, there are a few changes that are required in order to work:\n\nFirst, remove these lines:\n\n[source,rust]\n----\n#![feature(proc_macro_hygiene)]\n\n// …\n\nuse tql_macros::sql;\n----\n\nAnd add the following line before `extern crate tql`:\n\n[source,rust]\n----\n#[macro_use]\n----\n\nThis is how the start of the file now looks:\n\n[source,rust]\n----\nextern crate chrono;\nextern crate postgres;\n#[macro_use]\nextern crate tql;\n#[macro_use]\nextern crate tql_macros;\n\nuse postgres::{Connection, TlsMode};\nuse tql::PrimaryKey;\n----\n\nFinally, disable the `unstable` feature by updating the `tql` dependency to:\n\n[source,toml]\n----\n[dependencies.tql]\ndefault-features = false\nfeatures = [\"chrono\", \"pg\"]\nversion = \"0.1\"\n----\n\nWith this small change, we can use the `sql!()`, but it now requires you to specify the connection:\n\n[source.rust]\n----\nlet date_added = Utc::now();\nlet id = sql!(connection, Model.insert(text = text, date_added = date_added)).unwrap();\n----\n\nAlso, because of limitations on the stable compiler, you cannot use an expression for the arguments anymore:\nthat's why we now create a variable `date_added`.\nFor now, if you use `tql` on stable, you need to use identifiers or literals for arguments.\n\n=== Why not always using the stable version?\n\nProcedural macros do not currently support emitting errors at specific positions on the stable version, so with this version, you will get errors that are less useful, like in the following output:\n\n[source]\n----\nerror[E0308]: mismatched types\n  --\u003e src/main.rs:47:18\n   |\n47 |     let result = sql!(Model.insert(text = text, date_added = Utc::now(), done = false));\n   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected \u0026str, found struct `std::string::String`\n   |\n   = note: expected type `\u0026str`\n              found type `std::string::String`\n   = help: try with `\u0026sql!(Model.insert(text = text, date_added = Utc::now(), done = false))`\n   = note: this error originates in a macro outside of the current crate\n----\n\nWhile you will get this nicer error when using the nightly version of Rust:\n\n[source]\n----\nerror[E0308]: mismatched types\n  --\u003e examples/todo.rs:49:46\n   |\n49 |     let result = sql!(Model.insert(text = text, date_added = Utc::now(), done = false));\n   |                                           ^^^^\n   |                                           |\n   |                                           expected \u0026str, found struct `std::string::String`\n   |                                           help: consider borrowing here: `\u0026text`\n   |\n   = note: expected type `\u0026str`\n              found type `std::string::String`\n----\n\nSo, a good workflow is to develop on nightly and then ship on stable.\nThis way, you get the best of both worlds:\nyou have nice errors and you can deploy with the stable version of the compiler.\nThis is not an issue at all because you're not supposed to have compiler errors when you're ready to deploy (and you can see the errors anyway).\n\nNOTE: Compile with `RUSTFLAGS=\"--cfg procmacro2_semver_exempt\"` to get even better error messages.\n\n== Syntax table\n\nThe left side shows the generated SQL and the right side shows the syntax you can use with `tql`.\n\n[cols=\"1a,1a\", options=\"header\"]\n|===\n| SQL\n| Rust\n\n|\n[source, sql]\n----\nSELECT * FROM Table\n----\n|\n[source, rust]\n----\nTable.all()\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table WHERE field1 = 'value1'\n----\n|\n[source, rust]\n----\nTable.filter(field1 == \"value1\")\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table WHERE primary_key = 42\n----\n|\n[source, rust]\n----\nTable.get(42)\n\n// Shortcut for:\n\nTable.filter(primary_key == 42)[0..1];\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table WHERE field1 = 'value1'\n----\n|\n[source, rust]\n----\nTable.get(field1 == \"value1\")\n\n// Shortcut for:\n\nTable.filter(field1 == \"value1\")[0..1];\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table WHERE field1 = 'value1' AND field2 \u003c 100\n----\n|\n[source, rust]\n----\nTable.filter(field1 == \"value1\" \u0026\u0026 field2 \u003c 100)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table WHERE field1 = 'value1' OR field2 \u003c 100\n----\n|\n[source, rust]\n----\nTable.filter(field1 == \"value1\" \\|\\| field2 \u003c 100)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table ORDER BY field1\n----\n|\n[source, rust]\n----\nTable.sort(field1)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table ORDER BY field1 DESC\n----\n|\n[source, rust]\n----\nTable.sort(-field1)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table LIMIT 0, 20\n----\n|\n[source, rust]\n----\nTable[0..20]\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table\nWHERE field1 = 'value1'\n  AND field2 \u003c 100\nORDER BY field2 DESC\nLIMIT 10, 20\n----\n|\n[source, rust]\n----\nTable.filter(field1 == \"value1\" \u0026\u0026 field2 \u003c 100)\n    .sort(-field2)[10..20]\n----\n\n|\n[source, sql]\n----\nINSERT INTO Table(field1, field2) VALUES('value1', 55)\n----\n|\n[source, rust]\n----\nTable.insert(field1 = \"value1\", field2 = 55)\n----\n\n|\n[source, sql]\n----\nUPDATE Table SET field1 = 'value1', field2 = 55 WHERE id = 1\n----\n|\n[source, rust]\n----\nTable.get(1).update(field1 = \"value1\", field2 = 55);\n\n// or\n\nTable.filter(id == 1).update(field1 = \"value1\", field2 = 55);\n----\n\n|\n[source, sql]\n----\nDELETE FROM Table WHERE id = 1\n----\n|\n[source, rust]\n----\nTable.get(1).delete();\n\n// ou\n\nTable.filter(id == 1).delete()\n----\n\n|\n[source, sql]\n----\nSELECT AVG(field2) FROM Table\n----\n|\n[source, rust]\n----\nTable.aggregate(avg(field2))\n----\n\n|\n[source, sql]\n----\nSELECT AVG(field1) FROM Table1 GROUP BY field2\n----\n|\n[source, rust]\n----\nTable1.values(field2).annotate(avg(field1))\n----\n\n|\n[source, sql]\n----\nSELECT AVG(field1) as average FROM Table1\nGROUP BY field2\nHAVING average \u003e 5\n----\n|\n[source, rust]\n----\nTable1.values(field2).annotate(average = avg(field1))\n    .filter(average \u003e 5)\n----\n\n|\n[source, sql]\n----\nSELECT AVG(field1) as average FROM Table1\nWHERE field1 \u003c 10\nGROUP BY field2\nHAVING average \u003e 5\n----\n|\n[source, rust]\n----\nTable1.filter(field1 \u003c 10).values(field2)\n    .annotate(average = avg(field1)).filter(average \u003e 5)\n----\n\n|\n[source, sql]\n----\nSELECT Table1.field1, Table2.field1 FROM Table1\nINNER JOIN Table2 ON Table1.pk = Table2.fk\n----\n|\n[source, rust]\n----\n#[derive(SqlTable)]\nstruct Table1 {\n    pk: PrimaryKey,\n    field1: i32,\n}\n\n#[derive(SqlTable)]\nstruct Table2 {\n    field1: i32,\n    fk: ForeignKey\u003cTable1\u003e,\n}\n\nTable1.all().join(Table2)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE YEAR(date) = 2015\n----\n|\n[source, rust]\n----\nTable1.filter(date.year() == 2015)\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE INSTR(field1, 'string') \u003e 0\n----\n|\n[source, rust]\n----\nTable1.filter(field1.contains(\"string\"))\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE field1 LIKE 'string%'\n----\n|\n[source, rust]\n----\nTable1.filter(field1.starts_with(\"string\"))\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE field1 LIKE '%string'\n----\n|\n[source, rust]\n----\nTable1.filter(field1.ends_with(\"string\"))\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE field1 IS NULL\n----\n|\n[source, rust]\n----\nTable1.filter(field1.is_none())\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE field1 REGEXP BINARY '\\^[a-d]'\n----\n|\n[source, rust]\n----\nTable1.filter(field1.regex(r\"\\^[a-d]\"))\n----\n\n|\n[source, sql]\n----\nSELECT * FROM Table1 WHERE field1 REGEXP '\\^[a-d]'\n----\n|\n[source, rust]\n----\nTable1.filter(field1.iregex(r\"\\^[a-d]\"))\n----\n\n|\n[source, sql]\n----\nCREATE TABLE IF NOT EXISTS Table1 (\n    pk INTEGER NOT NULL AUTO_INCREMENT,\n    field1 INTEGER,\n    PRIMARY KEY (pk)\n)\n----\n|\n[source, rust]\n----\n#[derive(SqlTable)]\nstruct Table1 {\n    pk: PrimaryKey,\n    field1: i32,\n}\n\nTable1.create()\n----\n|===\n\n== Donations\n\nIf you appreciate this project and want new features to be\nimplemented, please support me on Patreon.\n\nimage:https://c5.patreon.com/external/logo/become_a_patron_button.png[link=\"https://www.patreon.com/antoyo\"]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantoyo%2Ftql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantoyo%2Ftql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantoyo%2Ftql/lists"}