{"id":13933017,"url":"https://github.com/jonifreeman/sqltyped","last_synced_at":"2025-04-09T19:20:02.502Z","repository":{"id":4219403,"uuid":"5341013","full_name":"jonifreeman/sqltyped","owner":"jonifreeman","description":"Embedding SQL as an external DSL into Scala","archived":false,"fork":false,"pushed_at":"2016-11-19T18:20:39.000Z","size":684,"stargazers_count":291,"open_issues_count":5,"forks_count":15,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-04-09T19:19:57.724Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"OpenPLi/enigma2","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonifreeman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-08-08T11:54:29.000Z","updated_at":"2024-03-17T22:34:10.000Z","dependencies_parsed_at":"2022-09-26T22:10:17.235Z","dependency_job_id":null,"html_url":"https://github.com/jonifreeman/sqltyped","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonifreeman%2Fsqltyped","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonifreeman%2Fsqltyped/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonifreeman%2Fsqltyped/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonifreeman%2Fsqltyped/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonifreeman","download_url":"https://codeload.github.com/jonifreeman/sqltyped/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248094991,"owners_count":21046770,"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":[],"created_at":"2024-08-07T21:01:29.118Z","updated_at":"2025-04-09T19:20:02.480Z","avatar_url":"https://github.com/jonifreeman.png","language":"Scala","readme":"sqlτyped - a macro which infers Scala types by analysing SQL statements\n=======================================================================\n\n\n_Towards a perfect impedance match..._\n\n* The types and column names are already defined in database schema and SQL query. Why not use those and infer types and accessor functions?\n\n* SQL is a fine DSL for many queries. It is the native DSL of relational databases and wrapping it with another DSL is often unnecessary (SQL sucks when one has to compose queries, or if you have to be database agnostic).\n \n \n**sqlτyped converts SQL string literals into typed functions at compile time.**\n \n```sql\nselect age, name from person where age \u003e ?\n```\n        \n  ==\u003e\n\n```scala\nInt =\u003e List[{ age: Int, name: String }]\n```\n\n\nExamples\n--------\n\nThe following examples use schema and data from [test.sql](https://github.com/jonifreeman/sqltyped/blob/master/core/src/test/resources/test.sql)\n\nFirst some boring initialization... \n\nStart console: ```sbt```, then ```project sqltyped``` and ```test:console```.\n\n```scala\nimport java.sql._\nimport sqltyped._\nClass.forName(\"com.mysql.jdbc.Driver\")\nimplicit def conn = DriverManager.getConnection(\"jdbc:mysql://localhost:3306/sqltyped\", \n                                                \"root\", \"\")\n```\n\nNow we are ready to query the data.\n\n```scala\nscala\u003e val q = sql(\"select name, age from person\")\nscala\u003e q() map (_ get \"age\")\nres0: List[Int] = List(36, 14)\n```\n\nNotice how the type of 'age' was infered to be Int.\n\n```scala\nscala\u003e q() map (_ get \"salary\")\n\u003cconsole\u003e:24: error: No field String(\"salary\") in record ...\n               q() map (_ get \"salary\")\n```\n\nOops, a compilation failure. Can't access 'salary', it was not selected in the query.\n\nQuery results are returned as List of type safe records (think ```List[{name:String, age:Int}]```).\nAs the above examples showed a field of a record can be accessed with get function: ```row.get(name)```.\nFunctions ```values``` and ```tuples``` can be used to drop record names and get just the query values.\n\n```scala\nscala\u003e q().values\nres1: List[shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] = \n  List(joe :: 36 :: HNil, moe :: 14 :: HNil)\n\nscala\u003e q().tuples\nres2: List[(String, Int)] = List((joe,36), (moe,14))\n```\n\nInput parameters are parsed and typed.\n\n```scala\nscala\u003e val q = sql(\"select name, age from person where age \u003e ?\")\n\nscala\u003e q(\"30\") map (_ get \"name\")\n\u003cconsole\u003e:24: error: type mismatch;\n found   : String(\"30\")\n required: Int\n              q(\"30\") map (_ get name)\n\nscala\u003e q(30) map (_ get \"name\")\nres4: List[String] = List(joe)\n```\n\nNullable columns are inferred to be Scala Options.\n\n```scala\nscala\u003e val q = sql(\"\"\"select p.name, j.name as employer, j.started, j.resigned \n                      from person p join job_history j on p.id=j.person order by employer\"\"\")\nscala\u003e q().tuples\nres5: List[(String, String, java.sql.Timestamp, Option[java.sql.Timestamp])] = \n  List((joe,Enron,2002-08-02 12:00:00.0,Some(2004-06-22 18:00:00.0)), \n       (joe,IBM,2004-07-13 11:00:00.0,None))\n```\n\nFunctions are supported too. Note how function 'max' is polymorphic on its argument. For String\ncolumn it is typed as String =\u003e String etc.\n\n```scala\nscala\u003e val q = sql(\"select max(name) as name, max(age) as age from person where age \u003e ?\")\nscala\u003e q(10).tupled\nres6: (Option[String], Option[Int]) = (Some(moe),Some(36))\n```\n\n### Analysis ###\n\nSo far all the examples have returned results as Lists of records. But with a little bit of query\nanalysis we can do better. Like, it is quite unnecessary to box the values as records if just one \ncolumn is selected.\n\n```scala\nscala\u003e sql(\"select name from person\").apply\nres7: List[String] = List(joe, moe)\n\nscala\u003e sql(\"select age from person\").apply\nres8: List[Int] = List(36, 14)\n```\n\nThen, some queries are known to return just 0 or 1 values, a perfect match for Option type. \nThe following queries return possible result as an Option instead of List. The first query uses \na uniquely constraint column in its where clause. The second one explicitly wants at most one row.\n\n```scala\nscala\u003e sql(\"select name from person where id=?\").apply(1)\nres9: Some[String] = Some(joe)\n\nscala\u003e sql(\"select age from person order by age desc limit 1\").apply\nres10: Some[Int] = Some(36)\n```\n\n### Inserting data ###\n\n```scala\nscala\u003e sql(\"insert into person(name, age, salary) values (?, ?, ?)\").apply(\"bill\", 45, 30000)\nres1: Int = 1\n```\n\nReturn value was 1, which means that one row was added. However, often a more useful return value \nis the generated primary key. Table 'person' has an autogenerated primary key column named 'id'. To get\nthe generated value use a function ```sqlk``` (will be changed to ```sql(..., keys = true)``` once \n[Scala macros](https://issues.scala-lang.org/browse/SI-5920) support default and named arguments).\n\n```scala\nscala\u003e sqlk(\"insert into person(name, age, salary) values (?, ?, ?)\").apply(\"jill\", 45, 30000)\nres2: Long = 3\n```\n\nInserting multiple values is supported too.\n\n```scala\nscala\u003e sqlk(\"insert into person(name, age, salary) select name, age, salary from person\").apply\nres3: List[Long] = List(4, 5, 6)\n```\n\nUpdates work as expected.\n\n```scala\nscala\u003e sql(\"update person set name=? where age \u003e= ?\").apply(\"joe2\", 30)\nres4: Int = 1\n```\n\n\nDocumentation\n-------------\n\nSee [wiki](https://github.com/jonifreeman/sqltyped/wiki).\n\n[Demo app](https://github.com/jonifreeman/sqltyped/tree/master/demo)\n\nHow to try it?\n--------------\n\n### Install ###\n\nRequires at least Scala 2.10.2 and SBT 0.13.\n\nsqlτyped is published to Sonatype repositories.\n\n```scala\n\"fi.reaktor\" %% \"sqltyped\" % \"0.4.3\"\n```\n\n### Build ###\n\n    git clone https://github.com/jonifreeman/sqltyped.git\n    cd sqltyped\n\nThen either:\n\n    mysql -u root -e 'create database sqltyped'\n    mysql -u root sqltyped \u003c core/src/test/resources/test.sql\n\nor:\n\n    sudo -u postgres createuser -P sqltypedtest  // Note, change the password from project/build.scala\n    sudo -u postgres createdb -O sqltypedtest sqltyped\n    sudo -u postgres psql sqltyped \u003c core/src/test/resources/test-postgresql.sql\n\nTo run the tests you need to setup both databases.\n\nCredits\n-------\n\n*(in order of appearance)*\n\n* Joni Freeman\n* Dylan Alex Simon\n* Vassil Dichev\n","funding_links":[],"categories":["Scala","\u003ca name=\"Scala\"\u003e\u003c/a\u003eScala"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonifreeman%2Fsqltyped","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonifreeman%2Fsqltyped","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonifreeman%2Fsqltyped/lists"}