{"id":19848661,"url":"https://github.com/polterguy/magic.data.common","last_synced_at":"2025-05-01T22:30:34.781Z","repository":{"id":44558922,"uuid":"211121888","full_name":"polterguy/magic.data.common","owner":"polterguy","description":"Commonalities for MS SQL and MySQL Magic data adapters","archived":false,"fork":false,"pushed_at":"2024-01-22T09:21:38.000Z","size":387,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T18:50:35.592Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ainiro.io","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/polterguy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-09-26T15:26:47.000Z","updated_at":"2023-09-03T08:24:38.000Z","dependencies_parsed_at":"2023-11-12T11:26:44.025Z","dependency_job_id":"ec93844c-b54a-4c2b-a38b-a01ea6fe78f5","html_url":"https://github.com/polterguy/magic.data.common","commit_stats":null,"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.data.common","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.data.common/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.data.common/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/polterguy%2Fmagic.data.common/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/polterguy","download_url":"https://codeload.github.com/polterguy/magic.data.common/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251954678,"owners_count":21670849,"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-11-12T13:17:55.600Z","updated_at":"2025-05-01T22:30:34.381Z","avatar_url":"https://github.com/polterguy.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# magic.data.common - Hyperlambda slots to access your SQL database\n\nThe magic.data.commonproject is the generic data adapter, that transform dynamically from a lambda node\nstructure into SQL, and polymorphistically invokes your specialised data adapter, resulting in SQL\nstatements executed towards your database type of choice. In addition, it contains helper slots to give\nyou more _\"raw\"_ database access, in addition to slots helping you to open database connections,\ncreate transactions, execute SQL, etc.\n\n## How to use [sql.*]\n\nThese slots never executes SQL towards your data adapter, but rather simply generates your SQL, and\nreturns the results of the SQL generation process back to you. They're mostly intended for debugging\npurposes, and/or learning purposes, and can be interchanged with their **[data.xxx]** equivalent,\nand/or their **[mysql.xxx]**/**[mssql.xxx]**/**[pgsql.xxx]** equivalent, etc. You can substitute\nthese slots with for instance **[data.xxx]** if you wish to actually execute some SQL towards your\ndatabase adapter of choice.\n\n## How to use [data.*]\n\nAll of the **[data.xxx]** slots are actually just polymorphistically evaluating your specialised adapter's\nslots, such as for instance **[data.connect]**, that will invoke **[mysql.connect]** if this is your default\ndatabase. However, all of these lots can also be given an explicit **[database-type]** argument, being for\ninstance _\"mssql\"_, allowing you to choose a database type explicitly as you invoke it.\n\nIf you don't provide an explicit **[database-type]** argument to these slots, the default database\ntype will be retrieved from your _\"appsettings.json\"_ file, from the `magic:databases:default` value,\nand substitute the _\"data\"_ parts with the value found from your configuration setting, to invoke\nyour specialised implementation. For instance, if you're using MySQL as your default database type,\nand you invoke for instance **[data.connect]**, this will transform into an invocation to **[mysql.connect]**,\nallowing you to use generic database slot invocations, ignoring your database type, creating the correct\nSQL dialect for you automagically.\n\n### How to use [data.connect]\n\nThis slot will open a database connection for you. You can pass in a complete connection string (not recommended),\nor only the database name if you wish. If you pass in only the database name, the generic connection string for your\ndatabase type of choice from your _\"appsettings.json\"_ file will be used, substituting its `{database}` parts\nwith the database of your choice.\nInside of this slot, which actually is a lambda **[eval]** invocation, you can use any of the other slots, requiring\nan existing and open database connection to function. You can see an example below.\n\n```\ndata.connect:sakila\n   data.read\n      table:actor\n```\n\nSince the **[data.connect]** slot actually takes a lambda object, you can also add any amount\nof other lambda invocations inside of the lambda object supplied to the slot, allowing you to for instance\ncreate loops, conditional executions, etc, _inside_ of your invocation to **[data.connect]**. This is also\ntrue for all other slots taking a lambda object, such as for instance **[data.transaction.create]**.\nInside your lambda object, an invocation towards your database such as e.g. **[data.read]**, will be\nusing this database connection, as long as the type of database is matching. The database connection will\nbe kept open, and implicitly used, for the entirety of the lambda object. If you need another database\nconnection inside of your lambda object, you'll need to nest **[data.connect]** invocations.\nYou can also explicitly choose which connection string to use as you open a connection, by separating the\nconnection string and the database name by a `|` symbol, and wrapping your entire value inside\nof brackets. If you have a connection string in your _\"appsettings.json\"_ file named for instance _\"foo\"_,\nand this connection string points to a server instance having a database named _\"bar\"_, you could open\na connection to this database using something resembling the following.\n\n```\ndata.connect:[foo|bar]\n```\n\nThis is why your connection strings should contain the `{database}` as a generic argument, since the slot\nsubstitutes the `{database}` parts dynamically as you create new connections. Below is an example of such\na connection string configuration setting.\n\n```\n\"generic\": \"Server=localhost\\\\SQLEXPRESS;Database={database};Trusted_Connection=True;\"\n```\n\n### How to use [data.select]\n\nThis slot allows you to pass in any arbitrary SQL you wish, and evaluate it to a `DataReader`, and return\nall records as a lambda object. You can find an example below.\n\n```\ndata.connect:sakila\n   data.select:select first_name, last_name from actor limit 5\n```\n\nAssuming you have the _\"sakila\"_ database from Oracle installed in your database, and your default\ndatabase type is MySQL, the result of the above will end up looking like the following.\n\n```\ndata.connect\n   data.select\n      \"\"\n         first_name:PENELOPE\n         last_name:GUINESS\n      \"\"\n         first_name:NICK\n         last_name:WAHLBERG\n      \"\"\n         first_name:ED\n         last_name:CHASE\n      \"\"\n         first_name:JENNIFER\n         last_name:DAVIS\n      \"\"\n         first_name:JOHNNY\n         last_name:LOLLOBRIGIDA\n```\n\nThis slot requires SQL resembling your specialised database type of dialect, and will\nnot in any ways transpile the SQL towards your specific underlaying database type's SQL dialect.\nIf you can, you should rather use **[data.read]**, to avoid lockin towards a specific database\nvendor's SQL dialect.\nYou can also select multiple result sets if you have batch type of SQL statements, containing\nmultiple SQL statements, and you want to return the result of all SQL statements you're executing.\nYou do this by providing a **[multiple-result-sets]** argument and set its value to boolean true.\nIf you do this, the slot will return an array of arrays, one outer array for each result set\nyour SQL generates. Below is an example.\n\n```sql\ndata.select:\"select * from table1; select * from table2;\"\n   multiple-result-sets:bool:true\n```\n\nWhich would result in something resembling the following.\n\n```\n   data.select\n      \"\"\n         \"\"\n            field1:foo1\n            field2:bar1\n         \"\"\n            field1:foo2\n            field2:bar2\n      \"\"\n         \"\"\n            field1:foo1\n            field2:bar1\n         \"\"\n            field1:foo2\n            field2:bar2\n```\n\n### How to use [data.scalar]\n\nThis slot is similar to the **[data.select]** slot, but will only return one value as the\nvalue of its node after execution. This slot is typically used for aggregate results. You can see\nan example below.\n\n```\ndata.connect:sakila\n   data.scalar:select count(*) from actor\n```\n\nAfter execution, your result will resemble the following.\n\n```\ndata.connect\n   data.scalar:long:200\n```\n\nYet again you should prefer the **[data.\\*]** slots if you can.\n\n### How to use [data.execute]\n\nThis slot should be used if you don't expect any type of result at all, such as in for instance\ndelete or update invocations, where you don't care about the result of the operation. You can\nfind an example below.\n\n```\ndata.connect:sakila\n\n   // Notice, will throw! (hopefully!)\n   data.execute:delete from non_existing_table\n```\n\nYet again, prefer **[data.delete]** if you can.\n\n### Database transactions\n\nAlthough you should be careful with database transactions, sometimes you _really_ need them. For those cases you\ncan use the following 3 slots to create, rollback, and/or commit transactions towards your underlaying database.\n\n* __[data.transaction.create]__ - Creates a new database transaction\n* __[data.transaction.commit]__ - Commits an existing open transaction\n* __[data.transaction.rollback]__ - Roll back an existing open transaction\n\n**Notice** - The default logic for a database transaction, is that unless it's _explicitly committed_\nbefore leaving scope, it will roll back by default. Below is an example of a transaction that will\nrollback, since it's not explicitly commited before leaving scope.\n\n```\ndata.connect:sakila\n   data.transaction.create\n      data.execute:delete from film_actor\n\n      /*\n       * If you uncomment the line below the\n       * transaction will be committed, resulting\n       * in that everything from your film_actor\n       * table will be deleted.\n       */\n      //data.transaction.commit\n\ndata.connect:sakila\n\n   /*\n    * Notice, this still returns 5462 items, since\n    * transaction was implicitly rolled back above.\n    */\n   data.scalar:select count(*) from film_actor\n```\n\nA transaction typically follows your connection, implying to count items\nafter the transaction has been rolled back, we'll need a _new_ connection, as the\nabove example illustrates.\n\n## How to use [sql.*] slots\n\nAll of these slots simply generates SQL for you, using the _generic_ SQL dialect syntax,\nwhich might or might not work for your database adapter of choice. This allows you to create\nSQL statements _without_ executing anything towards your database. This allows you to play\naround with the syntax, to understand how it works, and see how some semantic graph object\nresults in an SQL statement before using it.\nAll of these slots have **[data.\\*]** equivalent slots, which again polymorphistically\ninvokes your specialised data adapter's equivalent, and/or can be parametrised with a\ndatabase type - Which again resolves to the **[mysql.\\*]** equivalent if you supply\n_\"mysql\"_ as your **[database-type]**, and/or MySQL is your default database type\nas configured in your _\"appsettings.json\"_ file.\nHence, the documentation for these slots is also the documentation for your **[data.\\*]**\nslots.\n\n### How to use [sql.create]\n\nThis slot will generate the SQL necessary to insert a record into a database for you. Besides the table\nargument, this slot can only be given one argument, which is __[values]__. Below is an example of usage.\n\n```\nsql.create\n   table:table1\n   values\n      field1:howdy\n      field2:world\n```\n\nNotice, to avoid SQL injection attacks, this slot will always return parameters expected to be passed in\nfrom any potentially malicious clients as SQL parameters - Hence, the complete returned value of the\nabove Hyperlambda will be as follows.\n\n```\nsql.create:insert into 'table1' ('field1', 'field2') values (@0, @1)\n   @0:howdy\n   @1:world\n```\n\nThe basic idea is that everything that might be dynamically injected into your data access layer,\nshould be consumed as `SqlParameters`, or something equivalent, to prevent SQL injection attacks\ntowards your database. This is true for all arguments passed in as data for all slots in the project.\nThe slot will in its specialized implementations return the ID of the inserted record if possible,\nunless you explicitly parametrize it with a **[return-id]** argument and set its value to boolean\n`false`.\n\n### How to use [sql.read]\n\nThis slot requires only one mandatory argument, being your table name. The slot creates a select\nSQL statement for you. An example can be found below.\n\n```\nsql.read\n   table:foo\n```\n\nThe above will result in the following SQL returned to you. If you're using the special implementations,\nsuch as e.g. **[data.read]**, and/or **[mssql.read]** - The returned SQL might vary according to your dialect.\nBut the results of executing the SQL will be the same.\n\n```\nselect * from 'foo' limit 25\n```\n\nTo avoid accidentally exhausting your database, this slot will by default limit your result set\nto 25 records unless explicitly overridden by your code with a **[limit]** argument. You can optionally\nsupply the following arguments to this slot.\n\n* __[columns]__ - Columns to select\n* __[order]__ - Which column(s) to order the results by. You can supply multiple **[order]** arguments\n* __[direction]__ - Which direction to order your columns\n* __[limit]__ - How many records to return, default is 25. Set this value to -1 to avoid having the parser inject its default value\n* __[offset]__ - Offset of where to start returning records\n* __[where]__ - Where condition\n* __[join]__ - Join condition\n* __[group]__ - Group by declaration\n\nFor instance, to select only the _\"field1\"_ column and the _\"field2\"_ column from _\"table1\"_,\nand ordering descendingly by _\"field3\"_ - You can use something resembling the following.\n\n```\nsql.read\n   table:table1\n   columns\n      field1\n      field2\n   order:field3\n   direction:desc\n```\n\nThis will result in the following SQL returned.\n\n```\nselect 'field1','field2' from 'table1' order by 'field3' desc limit 25\n```\n\nThe **[direction]** argument can _only_ be either _\"asc\"_ or _\"desc\"_, implying ascending or descending.\nYou can also supply multiple ordering columns, by separating them by _\",\"_. See an example below, which also specifies\nwhat table to use while ordering your results.\n\n```\nsql.read\n   table:table1\n   order:table1.field1, table1.field2\n```\n\nThe above will result in the following SQL.\n\n```sql\nselect * from 'table1' order by 'table1'.'field1' asc,'table1'.'field2' asc limit 25\n```\n\nAnd finally you can provide the **[direction]** argument on separate **[order]** arguments, such as the\nfollowing illustrates.\n\n```\nsql.read\n   table:table1\n\n   order:field1\n      direction:asc\n\n   order:field2\n      direction:desc\n```\n\nIf you do, the ordering is sequentially applied.\n\n### Aggregate results\n\nYou can also create aggregate results, by simply adding your aggregate as your column, such as the\nfollowing illustrates. The reasons why this works, is because if the SQL generator finds a paranthesis in your\ncolumn declaration, it will simply ignore parsing that column altogether, and directly inject it into the\nresulting SQL's columns declaration.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   columns\n      count(*)\n        as:count\n```\n\nThe above will result in the following SQL.\n\n```\nselect count(*) as count from 'table1'\n```\n\nBy setting **[limit]** to _\"-1\"_, like we do above, we avoid adding the limit parts to our SQL. Unless\nyou explicitly specify a limit, the default value will always be 25 to avoid accidentally exhausting your database,\nand/or web server, by selecting all records from a table with millions of records.\n\n### Paging\n\nTo page your **[sql.read]** results, use **[limit]** and **[offset]**, such as the following illustrates.\nEven though we use _\"limit\"_ and _\"offset\"_, the correct syntax will be applied for your database type,\ndepending upon which database type you're using - Implying for Microsoft SQL Server, it will inject MS SQL dialect,\nand not MySQL dialect. But the syntax for your lambda object still remains the same, making it simpler to create\nSQL dialect valid for your specific database type 100% transparently.\n\n```\nsql.read\n   table:table1\n   offset:5\n   limit:10\n```\n\nThe above will return the following SQL `select * from 'table1' limit 10 offset 5`. If you run the above lambda\ntowards Microsoft SQL server, SQL syntax specific for MS SQL will be generated.\n\n### Aliasing column results\n\nYou can also extract columns with an alias, _\"renaming\"_ the column in its result, such as the following illustrates.\n\n```\nsql.read\n   table:table1\n   columns\n\n      table1.foo1\n         as:howdy\n\n      table1.foo2\n         as:world\n```\n\nThe above Hyperlambda will result in the following SQL.\n\n```sql\nselect 'table1'.'foo1' as 'howdy','table1'.'foo2' as 'world'\n   from 'table1' limit 25\n```\n\nEffectively resulting in that you'll have two columns returned after executing the above SQL, which\nare `howdy` and `world`. Combining this with the join features from this project, allows you to create\nany type of _\"projections\"_ you wish.\n\n### Joins\n\nThe project supports joins by parametrizing your **[sql.read]** invocation with **[join]** arguments, beneath your\n**[table]** argument(s). You can _only_ add **[join]** beneath **[table]** for **[sql.read]** invocations though.\nIf you have created the Sakila example database from Oracle, and you're using MySQL as your default database type,\nyou can execute the following MySQL join SQL statement to see a fairly complex recursive join.\n\n```\ndata.connect:sakila\n   data.read\n\n      columns\n         title\n         description\n         last_name\n         first_name\n\n      table:film\n\n         join:film_actor\n            type:inner\n            on\n               and\n                  film.film_id:film_actor.film_id\n\n            join:actor\n               type:inner\n               on\n                  and\n                     film_actor.actor_id:actor.actor_id\n```\n\nThe above lambda assumes you've got Oracle's Sakila database in your MySQL instance. If you only wish to see\nits resulting SQL, add a **[generate]** argument to the above root invocation, and set its value to _\"true\"_.\nAll specialised slots, dynamically building and executing some SQL towards your database, supports\nthe **[generate]** argument, allowing you to easily _\"debug\"_ your SQL statements, and see what they actually do.\nBelow is an example of the SQL created by the above invocation if you add a **[generate]** argument to it\nand set its value to boolean `true`.\n\n```sql\nselect `film`.`title`, `film`.`description`, `actor`.`last_name`, `actor`.`first_name` from `film`\n   inner join `film_actor` on `film`.`film_id` = `film_actor`.`film_id`\n      inner join `actor` on `film_actor`.`actor_id` = `actor`.`actor_id`\n   limit 25\n```\n\nYou can recursively join as many levels as you wish, in addition to also supplying multiple join conditions for\nthe same join. An example of the latter can be found below.\n\n```\nsql.read\n   generate:true\n   limit:-1\n   table:table1\n\n      join:table2\n         on\n            and\n               fk1:pk1\n               fk2:pk2\n```\n\nThe above lambda will result in the following SQL being generated.\n\n```sql\nselect * from 'table1'\n   inner join 'table2' on 'table1'.'fk1' = 'table2'.'pk1' and\n      'table1'.'fk2' = 'table2'.'pk2'\n```\n\nJoining tables works _almost_ the exact same way as using a **[where]** argument, allowing you\nto supply an operator for your join, such as we illustrate below, where we're using the `!=` operator,\ninstead of the (default) equality comparison. However, when you create your **[join]** segments\nyou can _only_ have columns from tables reference columns from tables, and not add static arguments\nto your join. If you need static arguments you have to add these into your **[where]** parts.\nSee the **[where]** criteria for details about comparison operators.\nYou can also explicitly choose a **[type]** of join, such as we illustrate below.\n\n```\nsql.read\n   limit:-1\n   table:table1\n\n      join:table2\n         type:inner\n         on\n            and\n               fk1.neq:pk1\n```\n\nThe above results in the following SQL.\n\n```sql\nselect * from 'table1' inner join 'table2' on 'table1'.'fk1' != 'table2'.'pk1'\n```\n\nThe **[type]** argument to your **[join]** arguments, can be _\"inner\"_, _\"full\"_, _\"left\"_ or _\"right\"_,\nresulting in the equivalent type of join for your SQL.\n\n### Differences between [join] and [where]\n\nThere is one crucial semantic difference between a **[join]** condition and a **[where]** condition, which is\nthat the library assumes a join is _always_ between two columns, while a where always assume you're _always_\ncomparing against a static value. This implies that you _cannot_ add static values into your SQL as a part of\nyour **[join]** condition, while the opposite is true for a **[while]**.\n\nAlthough this technically doesn't allow you to create any SQL you want to create, it is still more\nin _\"the spirit\"_ of SQL as a standard - And you can always add your static conditions into your **[where]**\nparts, while adding your table comparisons into your **[join]** conditions. This allows you to create\nany _result_ you want to achieve, although technically not any _SQL_ you want to have.\n\n#### Explicit arguments declarations for joins\n\nNormally you don't need to worry about this, but sometimes you need to explicitly add an argument to\nyour CRUD slot invocations if it has a `join` part, and you want one of your\nconditions for your join to be a static value of some sort, and not a comparison to your right hand side\ntable column. This can be accomplished with something such as the following.\n\n```\nsql.read\n   generate:true\n   table:foo\n      join:bar\n         on\n            and\n               foo.field1:bar.field2\n               foo.field2:@static-value\n   @static-value:static value\n```\n\nNotice the above **[@static-value]** parts, which becomes a normal argument, instead of trying to join\ntwo fields on two table. The above results in the following lambda being generated.\n\n```\ndata.read:select * from `foo` inner join `bar` on `foo`.`field1` = `bar`.`field2` and `foo`.`field2` = @static-value limit 25\n   @static-value:static value\n```\n\nAs you can see in the above result, the `@static-value` becomes a *statically* declared condition to your join,\nand not assumed to be a reference to a field in your right hand side joined table. This is one of those edge cases\nyou normally rarely need, but might be useful on rare occasions. The reasons why this works is because the RHS side\nof your join condition starts with an `@` character, which assumes you are referencing an argument and not a field\nin your joined table. This only has effects on your **[join]** parts, implying **[xxx.read]** slot invocations,\nsince these are the only slots supporting joins.\nThe above is the only exception that allows you to join on static values and not column names. However, as a general\nrule of thumb, we encourage users to **[join]** on table columns and add static values into your **[where]** conditions.\n\n### 'Namespacing' columns\n\nWhen you're joining results from multiple tables, it's often required that you specify which table you want some resulting\ncolumn to be fetched from, to avoid confusing your database as to which column you want to extract, in cases where the\nsame column exists in multiple tables. For such cases, you can simply refer to your table first, and then the column\nfrom that table, and separate your entities by a `.`. You can see an example of this below.\n\n```\ndata.connect:sakila\n   data.read\n      columns\n\n         /*\n          * Prefixing result columns with table names.\n          */\n         film.title\n         film.description\n         actor.last_name\n         actor.first_name\n\n      table:film\n\n         join:film_actor\n            type:inner\n            on\n               and\n                  film.film_id:film_actor.film_id\n\n            join:actor\n               type:inner\n               on\n                  and\n                     film_actor.actor_id:actor.actor_id\n```\n\nThe above will result in the following SQL, if you append the **[generate]** argument, and set its value to _\"true\"_.\n\n```sql\nselect\n   `film`.`title`,\n   `film`.`description`,\n   `actor`.`last_name`,\n   `actor`.`first_name`\n   from `film`\n      inner join `film_actor` on `film`.`film_id` = `film_actor`.`film_id`\n         inner join `actor` on `film_actor`.`actor_id` = `actor`.`actor_id`\n   limit 25\n```\n\nSpacing is not applied to the actual generated SQL result, but have been applied to some of the SQL\nexamples in this documentation to make the SQL more readable.\nIf you supply **[as]** arguments to your tables, you can also use your alias to reference tables inside of\nyour invocation.\n\n### Group by\n\nYou can also provide a **[group]** argument to your lambda, resulting in a _\"group by\"_ statement injected\ninto the resulting SQL. Below is an example.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   columns\n      col1\n      count(*)\n         as:count\n   group\n      col1\n```\n\nThe above will result in the following SQL.\n\n```\nselect col1, count(*) as count from 'table1' group by 'col1'\n```\n\nYou can supply multiple group by columns, in addition to _\"namespacing\"_ your columns with your table names,\nsuch as we illustrate below.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   columns\n      count(*)\n   group\n      table1.foo1\n      table1.foo2\n```\n\nThe above of course results in the following SQL.\n\n```sql\nselect count(*) from 'table1' group by 'table1'.'foo1','table1'.'foo2'\n```\n\nYou can of course combine your **[group]** arguments with **[where]** arguments, and **[join]** arguments,\nallowing you to create complex aggregate results, statistics, joining multiple tables, etc.\n\n### How to use [sql.update]\n\nThis slot allows you to update one or more records, in a specified **[table]**. Just like create, it requires\none mandatory argument, being **[values]**, implying columns/values you wish to update. This slot also takes\nan optional **[where]** argument, which is described further down in this document. Its simplest version can be\nimagined as follows.\n\n```\nsql.update\n   table:table1\n   values\n      field1:howdy\n```\n\nThe above of course will result in the following.\n\n```sql\nsql.update:update 'table1' set 'field1' = @v0\n   @v0:howdy\n```\n\n**Notice**, if you don't apply a **[where]** argument, then *all* records in your table will\nbe updated - Which is highly unlikely what your intentions are. Hence, make sure you apply a\n**[where]** argument as you invoke this slot.\n\n### How to use [sql.delete]\n\nThis slot is for deleting records. Its **[where]** argument is applied in a similar fashion as the where\nargument to **[sql.select]** and **[sql.update]**. You can find an example further down in this document of\nhow to use **[where]**.\n\n```\nsql.delete\n   table:table1\n   where\n      and\n         field1:value1\n         field2:value2\n```\n\nThe above will produce the following results.\n\n```\nsql.delete:delete from 'table1' where 'field1' = @0 and 'field2' = @1\n   @0:value1\n   @1:value2\n```\n\n### The [where] argument\n\nThis argument is common for **[sql.update]**, **[sql.delete]**, and **[sql.read]**, in addition\nto that a **[join]** will also be logically parsed much the same way as a **[where]** argument. The where\nargument follows a recursive structure, allowing you to supply multiple layers of `where` criteria,\nbeing applied recursively, using some sort of comparison operator, applied to all conditions in the\nsame level. Its most basic usage is as follows.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   where\n\n      and\n         field1:howdy\n```\n\nThe above would result in the following result.\n\n```\nsql.read:select * from 'table1' where 'field1' = @0\n   @0:howdy\n```\n\nTo apply multiple **[and]** criteria, you can simply add them consecutively as follows.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   where\n\n      and\n         field1:howdy\n         field2:world\n```\n\nThe above results in the following.\n\n```\nsql.read:select * from 'table1' where 'field1' = @0 and 'field2' = @1\n   @0:howdy\n   @1:world\n```\n\nIf you exchange the above **[and]** with **[or]**, the system will use the `or` operator to\nseparate your arguments, such as the following illustrates.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   where\n\n      or\n         field1:howdy\n         field2:world\n```\n\nThe above results in the following result.\n\n```\nsql.read:select * from 'table1' where 'field1' = @0 or 'field2' = @1\n    @0:howdy\n    @1:world\n```\n\nYou can also nest operators, producing paranthesis, and create complex conditions, such as the following\nillustrates.\n\n```\nsql.read\n   table:table1\n   limit:-1\n   where\n\n      or\n         field1:howdy\n\n         and\n            field2:world\n            field3:dudes\n```\n\nWhich results in the following.\n\n```\nsql.read:select * from 'table1' where 'field1' = @0 or ('field2' = @1 and 'field3' = @2)\n   @0:howdy\n   @1:world\n   @2:dudes\n```\n\nThe parent of a list of criteria is deciding which logical operator to separate your conditions\nwith, contrary to traditional languages, where you separate your conditions with the logical operator, and\nexplicitly add paranthesis to group your levels. This might seem a little bit weird in the beginning,\nbut this is a general rule with everything in Hyperlambda, and after a while will feel more natural than\nthe alternative. The reasons for this is to allow for semantically traversing your lambda objects, allowing\nthe computer to logically understand what it does more easily - Among other things. Think of the boolean\nlogical parts of your SQL slots as _\"grouping your comparisons\"_ or a _\"logical function invocation\"_ if it helps.\n\n### Comparison operators\n\nThe project supports the following comparison operators.\n\n* `eq` - Equality comparison, equivalent to `=`\n* `neq` - Not equality comparison, equivalent to `!=`\n* `mt` - More than comparison, equivalent to `\u003e`\n* `lt` - Less than comparison, equivalent to `\u003c`\n* `lteq` - Less than or equal comparison, equivalent to `\u003c=`\n* `mteq` - More than or equal comparison, equivalent to `\u003e=`\n* `like` - Like comparison, equivalent to SQL's `like` comparison\n* `in` - Special comparison operator, since it requires a _list_ of values, generating an _\"in\"_ SQL condition\n\nEverywhere you need to compare one field with another, such as in **[where]** or **[join]**\narguments, you can append a comparison operator to your left hand side column, such as the following\nillustrates.\n\n```\nsql.read\n   table:foo\n   where\n\n      and\n         field1.neq:xxx\n```\n\nNotice the **.neq** parts above, and realise how the above will produce the following SQL.\n\n```sql\nselect * from 'foo' where 'field1' != @0\n```\n\nThe above **[field1.neq]** is substituted by the SQL generator to become a `!=` comparison operator\non the `field1` column versus the `xxx` argument. The comparison operator is *always* expected to be the\nlast parts of your _\"LHS\"_ (Left Hand Side) parts of your criteria - Implying the *name* of the node.\n\n### The [in] comparison operator\n\nThis operator is special, in that it doesn't require the caller to supply _one_ value, but rather a _list_ of values,\nfrom where the column you compare towards must have a value matching at least _one_ of these values. An example can\nbe found below.\n\n```\nsql.read\n   table:table1\n   where\n\n      and\n         table1.field1.in\n            :long:5\n            :long:7\n            :long:9\n```\n\nThe above will generate the following SQL, in addition to returning 3 parameters to the caller.\n\n```sql\nselect * from 'table1' where 'table1'.'field1' in (@0,@1,@2) limit 25\n```\n\n### Escaping characters\n\nIf you by some freak accident happen to have a column in one of your tables that is named for instance `neq`,\nyou can escape your column name, by prepending a `\\` to it. See an example below.\n\n```\nsql.read\n   table:table1\n   where\n\n      and\n         table1.\\neq:foo\n```\n\nNotice how the `\\` character above results in the following SQL.\n\n```sql\nselect * from 'table1' where 'table1'.'neq' = @0 limit 25\n```\n\nAs you can see, the `\\neq` is interpreted as a column name, and not a `neq` operator, and since the equality operator\nis the default selected if no operator is supplied, the comparison operator becomes `=`.\n\nYou can also escape columns entirely, if you for instance have a column that contains a `.` in its name,\nsuch as we illustrate below.\n\n```\nsql.read\n   table:table1\n   where\n\n      and\n         \\foo.bar:bar\n```\n\nNotice how the above lambda will interpret the `foo.bar` parts as a column name, and not as\ncolumn _\"bar\"_ on _\"foo\"_. You can see the resulting SQL below.\n\n```sql\nselect * from 'table1' where 'foo.bar' = @0 limit 25\n```\n\n### Extension comparison operators\n\nYou can also extend the existing comparison operators with your own, such as for instance having `ltmt`\nresulting in `\u003c\u003e`, etc. To do this, you'll have to register your comparison operator using the static\n`AddComparisonOperator` method on the `SqlWhereBuilder` class. Below is an example.\n\n```\nSqlWhereBuilder.AddComparisonOperator(\"ltmt\", (builder, args, colNode, escapeChar) =\u003e {\n    builder.Append(\" \u003c\u003e \");\n    SqlWhereBuilder.AppendArgs(args, colNode, builder, escapeChar);\n});\n```\n\nThe above will give you access to use `ltmt` as a comparison operator, resolving to `\u003c\u003e` in your SQL.\n\n### magic.data.common and meta data\n\nOne of the really nice things about this semantic approach to generating SQL is that it\nallows you to retrieve meta data from your Hyperlambda snippets, asking questions such as for instance\n_\"find all files that somehow selects columns from the 'xxx' table\"_ - And for that matter, even\ndynamically change the table name, using semantic refactoring and replacement concepts. Once you've\ncrossed the initial step into _meta data traversal_ in Hyperlambda, things like this becomes\nsecond hand nature with Hyperlambda.\n\n## SQL injection attacks\n\nThis project protects you automatically against SQL injection attacks, and protect values, and criteria, etc -\nBut you should _not_ allow any potentially insecure clients to dynamically declare which columns\nto select, and/or field _names_ for your `where` clauses. It will only protect your _values_,\nand _not_ table names or column names against SQL injection attacks.\n\nThe project does also _not_ verify that your SQL is possible to execute towards your database, such as verifying\nthat specified tables or columns actually exists. It does its best however, to verify that your Hyperlambda\nis structured correctly, and that it will create somewhat valid SQL - But you should *not assume* the SQL\nthe project generates is valid, before you have tested it.\n\n## Creating your own data adapter\n\nIf you wish to extend Magic to support a custom database type, you can do so using C# for instance.\nThis project contains 4 base classes, which you can inherit from to extend and implement your custom logic.\n\n* `SqlCreateBuilder` - Helper class to generate insert SQL statements\n* `SqlDeleteBuilder` - Helper class to generate delete SQL statements\n* `SqlReadBuilder` - Helper class to generate select SQL statements\n* `SqlUpdateBuilder` - Helper class to generate update SQL statements\n\nIf you create your own database implementation, you'll need to inherit from the above classes, and override\nwhatever parts of these classes that doesn't by default work as your database type needs it to work.\nIf you wish to do this, you would probably benefit from looking at one of the existing specialised\nimplementations, such as the MySQL, SQL Server or PostgreSQL specific implementations.\n\n## Magic's GitHub project page\n\nMagic is 100% Open Source and you can find the primary project GitHub page [here](https://github.com/polterguy/magic).\n\n## Magic's GitHub project page\n\nMagic is 100% Open Source and you can find the primary project GitHub page [here](https://github.com/polterguy/magic).\n\n## Project website for magic.data.common\n\nThe source code for this repository can be found at [github.com/polterguy/magic.data.common](https://github.com/polterguy/magic.data.common), and you can provide feedback, provide bug reports, etc at the same place.\n\n- ![Build status](https://github.com/polterguy/magic.data.common/actions/workflows/build.yaml/badge.svg)\n- [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=ncloc)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n- [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=polterguy_magic.data.common\u0026metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=polterguy_magic.data.common)\n\n## Copyright and maintenance\n\nThe projects is copyright Thomas Hansen 2023 - 2024, and professionally maintained by [AINIRO.IO](https://ainiro.io).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolterguy%2Fmagic.data.common","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpolterguy%2Fmagic.data.common","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpolterguy%2Fmagic.data.common/lists"}