{"id":18382705,"url":"https://github.com/stefanov-sm/restql","last_synced_at":"2025-04-14T04:15:04.930Z","repository":{"id":146063690,"uuid":"149505219","full_name":"stefanov-sm/restql","owner":"stefanov-sm","description":"restql - RESTful web services in SQL","archived":false,"fork":false,"pushed_at":"2023-12-08T11:18:54.000Z","size":371,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T04:14:59.711Z","etag":null,"topics":["database","jsonschema","postgresql","rest-api","sql","webservices"],"latest_commit_sha":null,"homepage":null,"language":"PHP","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/stefanov-sm.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}},"created_at":"2018-09-19T19:58:13.000Z","updated_at":"2024-04-25T09:54:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"f9569fe1-3fdf-4d51-a123-78d3c7b444c3","html_url":"https://github.com/stefanov-sm/restql","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/stefanov-sm%2Frestql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stefanov-sm%2Frestql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stefanov-sm%2Frestql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stefanov-sm%2Frestql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stefanov-sm","download_url":"https://codeload.github.com/stefanov-sm/restql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248819408,"owners_count":21166477,"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":["database","jsonschema","postgresql","rest-api","sql","webservices"],"created_at":"2024-11-06T01:07:32.515Z","updated_at":"2025-04-14T04:15:04.899Z","avatar_url":"https://github.com/stefanov-sm.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ***restql*** - RESTful web services in SQL\n### SQL query to RESTful service generator\n\n\n## About\n***restql*** is a simple __vanilla platform with no dependencies__ that wraps and exposes parameterized SQL queries as JSON web services in a flexible and secure way. \nThe implementation and sample service run on PHP and [PostgreSQL](https://www.postgresql.org/). Any other DBMS with PDO support and the corresponding PDO driver installed can be used.\n\nHere is an example. Consider this trivial query:\n```sql\nSELECT\n    current_number AS \"Tom\",\n    to_char(current_number, 'FMRN') AS \"Jerry\"\n FROM generate_series (1, 1000, 1) AS t (current_number)\n WHERE current_number BETWEEN :lower_limit AND :upper_limit\n LIMIT 100;\n```\nLet's make a web service out of it. In a web service environment arguments might be lower_limit = 15 and upper_limit = 19.\nAs a JSON object (this might be the service request data) they will look like this\n```json\n{\n \"lower_limit\": 15,\n \"upper_limit\": 19\n}\n```\nAfter the query is run then the raw resultset would be this\n```text\n|Tom|Jerry  |\n|---|-------|\n|15 |\"XV\"   |\n|16 |\"XVI\"  |\n|17 |\"XVII\" |\n|18 |\"XVIII\"|\n|19 |\"XIX\"  |\n```\nAs a JSON array (this would be part of the service response data) the resultset would be this\n```json\n[\n  {\n      \"Tom\": 15,\n      \"Jerry\": \"XV\"\n  },\n  {\n      \"Tom\": 16,\n      \"Jerry\": \"XVI\"\n  },\n  {\n      \"Tom\": 17,\n      \"Jerry\": \"XVII\"\n  },\n  {\n      \"Tom\": 18,\n      \"Jerry\": \"XVIII\"\n  },\n  {\n      \"Tom\": 19,\n      \"Jerry\": \"XIX\"\n  }\n]\n```\nThe service definition would comprise of the parameterized sql query in a text file and a service definition file called `\u003cservice_name\u003e.config.json` that references the sql file. One sql file may be referred to by more than one service definition.\nAdditionally post-processing may be done after the query has finished running (see below).  \nThe following response modes are supported:\n\n - returning the entire query rowset as a JSON array of objects\n - returning a single row as a JSON object\n - returning a single value as is\n - returning a JSON value\n - returning no result\n\n## Sample server deployment on Apache httpd, PHP and PostgreSQL\n- Download ***restql***;\n- Create a folder for your `\u003cbase_url\u003e` in the www folder;\n- Extract the ***restql*** files into it;\n- Modify `include/db.connection.config` to connect to your [PostgreSQL](https://www.postgresql.org/) database;\n- Modify or remove `include/db.account.config` (see below);\n- Either create an activity log database table and modify `include/logger.sql.config` accordingly (see below) or\n- rename/remove `include/logger.sql.config` to disable activity logging.  \n\nNow the details\n\n## Service generator constructor\n\nA service generator server is created via instantiation of the `Restql` class and providing paths to the server instance configuration folder and service configuration definitions folder.  \n`Restql::__construct(\u003cinstance config\u003e, \u003cservice config\u003e);`\n - `\u003cinstance config\u003e` - path to the server instance configuration folder without trailing path separator\n - `\u003cservice config\u003e` - path to the service definitions folder without trailing path separator\n\n## Server example\nIn this example `\u003cinstance config\u003e` is a folder called `include` above base folder and `\u003cservice config\u003e` is a folder called `services` above base folder.\n\n - Folder structure\n\n```text\n\u003cbase folder\u003e\n             ├file '.htaccess'\n             ├file 'restql.php'\n             ├folder 'include'\n             │       ├file 'restql.class.php'\n             │       ├file 'restql.helpers.class.php'\n             │       ├file 'db.connection.config'\n             │       ├file 'db.account.config'\n             │       └file 'logger.sql.config'\n             ├folder 'services'\n             │       ├file 'demo.config.json'\n             │       ├file 'demo.new.sql'\n             │       └file 'demo.postprocess.php'\n             └folder 'logs'\n                     └file 'debug.log.txt'\n```\n - File _restql.php_\n```php\n\u003c?php\n require(__DIR__.DIRECTORY_SEPARATOR.'include'.DIRECTORY_SEPARATOR.'restql.class.php');\n $svc = new Restql\n (\n \t__DIR__.DIRECTORY_SEPARATOR.'include', \n \t__DIR__.DIRECTORY_SEPARATOR.'services'\n );\n $svc -\u003e handle();\n```\n - File _restql.class.php_\n\n The main ***restql*** source file that contains Restql class definition.  \n Restql class has only one public method (except the constructor) - `Restql::handle()`\n \n## Server configuration\n\nServer configuration resides in a folder called `include` above base folder. It comprises of these files:\n - File _db.connection.config_ (mandatory)  \n   contains a PDO connection string **(for performance purposes consider connection pooling)**.\n\n```ini\npgsql:\n dbname=sampledb;\n host=172.30.0.100;\n port=5432;\n user=sampleUser;\n password=samplePassword;\n```\n\n - File _db.account.config_ (optional)  \n   contains database user credentials (username and password) in JSON format. Used when these are not contained in the connection string.  \n\n```json\n{\n \"username\": \"sampleUser\",\n \"password\": \"samplePassword\"\n}\n```\n\n - File _logger.sql.config_ (optional)  \n   contains a parameterized SQL query with exactly these three parameters:  \n   `call_by`, `call_resource`, `call_payload`\n\n```sql\ninsert into scratch.restql_log\n(\n call_by, call_resource, call_payload\n)\nvalues\n(\n :call_by, :call_resource, :call_payload\n);\n```\n - Sample log table DDL **(you must create one so that the logger SQL query can work)**\n\n```sql\ncreate table scratch.restql_log\n(\n  call_time timestamp not null default now(),\n  call_by text not null,\n  call_resource text not null,\n  call_payload text not null,\n  constraint restql_log_pkey primary key (call_time, call_by)\n);\n```\n## Service definition\n\nService definitions reside in `services` (i.e. `\u003cservice config\u003e`) folder. Each comprises of these three files:\n - `\u003cservice_name\u003e.config.json` - mandatory, contains service metadata\n - `\u003csql_file_name\u003e.sql` - mandatory, contains the service query\n - `\u003cpostprocess_file_name\u003e.php` - optional\n\n_The service example executes a parametrized SQL query and returns a table._  \n_See demo.config.json and demo.new.sql in the example below._\n\n`\u003csql_file_name\u003e.sql` file contains a single parameterized SQL query. Advanced SQL features (CTEs, window functions, etc.) and database server programming (stored procedures/functions) alike can be used in order to implement complex data logic.\n\n`\u003cservice_name\u003e.config.json` file contains service metadata and arguments' definitions.  \n\n___\n\n**NOTE:**\n- A JSON schema file for validation of `\u003cservice_name\u003e.config.json` files and a CLI script for generating runtime arguments' validation JSON schema are available in [resources](resources) folder. \n- A web GUI for testing and debugging is available in [restclient](examples/restclient) folder. \n\n___\n\n- File _demo.config.json_\n```json\n{\n\t\"settings\":\n\t{\n\t\t\"token\": \"PTn456KSqqU7WhSszSe\",\n\t\t\"query\": \"demo.new.sql\",\n\t\t\"response\": \"table\",\n\t\t\"postprocess\": false,\n\t\t\"iplist\": [\"172.30.0.0/25\", \"172.30.0.132\", \"127.0.0.1\"]\n\t},\n\t\"arguments\":\n\t{\n\t\t\"lower_limit\": {\"type\": \"number\", \"default\": 25},\n\t\t\"upper_limit\": {\"type\": \"number\", \"constant\": 30},\n\t\t\"label\":       {\"type\": \"text\",   \"default\": \"Just a label\", \"pattern\": \"/^[A-ZА-Я 0-9]+$/ui\"}\n\t}\n}\n```\n### *settings* section\n\n- **token** - mandatory text, a security token. The example was generated by [Random.org](https://www.random.org/passwords/?num=1\u0026len=24\u0026format=plain\u0026rnd=new).  \n The same token value must be used to both define and invoke the service.\n```json\nExample: \"token\": \"PTn456KSqqU7WhSszSe\"\n```\n- **query** - mandatory text  \n The file name of the service sql query\n```json\nExample: \"query\": \"demo.new.sql\"\n```\n\n- **response** - mandatory text, one of the predefined response modes listed below\n\n|mode| description|\n|---|---|\n|\"table\"| for row set returning queries. Rows are retrieved by `PDOStatement::fetchAll()` method and sent as an array of JSON objects|\n|\"row\"| for single row returning queries. A single row is retrieved by `PDOStatement::fetch()` method and sent as a JSON object|\n|\"value\"| for value returning queries. A single value is retrieved by `PDOStatement::fetchColumn()` method and sent as is|\n|\"jsonvalue\"| for json-returning queries. A single value is retrieved by `PDOStatement::fetchColumn()` method and sent as JSON|\n|\"void\"| no data is returned|\n\n```json\nExample: \"response\": \"table\"\n```\n - **postprocess** - optional text  \n The postprocess PHP file name (if any) or `false`\n```json\nExample: \"postprocess\":  \"demo.postprocess.php\"\n```\n - **iplist** - optional array of text representing IP ranges. If present then only caller IPs within these ranges are allowed\n```json\nExample: \"iplist\": [\"172.30.0.0/25\", \"172.30.0.132\", \"127.0.0.1\"]\n```\n\n### *arguments* section\n\nService arguments are defined as `\"argument_name\": \u003cargument description\u003e`\n\nargument description attributes:\n\n|Attribute | Required | Type | Description|\n|---|---|---|---|\n|\"type\"|Yes|text|Argument data type. One of \"number\", \"text\", \"boolean\"|\n|\"default\"|No|varying|Default value. Makes the service argument optional|\n|\"constant\"|No|varying|Non-overridable default value|\n|\"pattern\"|No|text|Regular expression for validation. Applicable to text arguments only|\n\n```text\nExample: \"lower_limit\": {\"type\": \"number\", \"default\": 25}\nExample: \"upper_limit\": {\"type\": \"number\", \"constant\": 30}\nExample: \"label\":       {\"type\": \"text\", \"default\": \"Just a label\", \"pattern\": \"/^[A-ZА-Я 0-9]+$/ui\"}\n```\n**NOTE:** Either \"default\" or \"constant\" or none of them may be specified but not both  \n**NOTE:** The `u` regex switch enables extended (cyrillic, greek, accented) characters matching.\n - File _demo.new.sql_\n\n```sql\nSELECT\n    cast(:label AS text) AS \"Етикет\",\n    current_number AS \"Tom\",\n    to_char(current_number, 'FMRN') AS \"Jerry\"\n FROM generate_series (1, 1000, 1) AS t (current_number)\n WHERE current_number BETWEEN :lower_limit AND :upper_limit\n LIMIT 100;\n```\n**NOTE:** Arguments in .config.json and parameters in .sql files must match exactly by name and number. SQL parameter names are prefixed with a colon (:).\n\n## A word of caution\n\nAlthough SQL injection is taken care about by using PDO prepared statements an extra line of defence is never one too many. Therefore using regular expression patterns for arguments validation in `\u003cservice_name\u003e.config.json` files is always a good idea.\n\n## A word on encoding\n\nAlways encode response JSON in UTF-8. The straightforward way to do this is to add `charset=utf8` in the `db.connection.config` file. This DSN setting is supported since PHP 5.3.6 and works for \n[Oracle](http://php.net/manual/en/ref.pdo-oci.connection.php), [SQL Server](http://php.net/manual/en/ref.pdo-dblib.connection.php) and [MySQL](http://php.net/manual/en/ref.pdo-mysql.connection.php) alike. For [PostgreSQL](https://www.postgresql.org/) (in this example) UTF-8 is usually the native client encoding.\n\n**NOTE:** Byte order marks (i.e. [BOM](https://en.wikipedia.org/wiki/Byte_order_mark)) in ***restql***-related files can cause a lot of trouble. Make sure that you save your files without a BOM.  \n\n## Post-processing\n\nIt is possible to invoke a PHP script after the database query has finished. This will not affect the sql query response.\nIn order to setup a post-process script `\u003cservice_name\u003e.config.json` file must contain the following option in the `settings` section:\n```json\n\"postprocess\": \"\u003cpostprocess_file_name\u003e.php\"\n```\nThe PHP script itself resides in file `\u003cpostprocess_file_name\u003e.php` in the `services` folder (i.e. `\u003cservice config\u003e`).  \n```php\nfunction postProcess($args, $response_data, $dbConn)\n/*\n$args - a tagged array of the service arguments\n$response_data - native 'data' part of the response\n$dbConn - the PDO connection\n*/\n``` \nmust be defined in the file. It will be called to do the post-processing. Whatever is returned by this function will be put into the service response as 'extra'.  \n\nA sample post-processing file called `demo.postprocess.php` is provided but not referenced in `demo.config.json` and therefore it does not get invoked. In order to do so change \n```json\n\"postprocess\": false\n```\nto\n```json\n\"postprocess\": \"demo.postprocess.php\"\n```\nin file `demo.config.json`.  \n\n**NOTE:** Use post-processing with utmost care\n\n## Logging\n\n- Activity is logged by invoking the SQL query in `include/logger.sql.config` file (if any) for every call;\n- Errors are logged in file `logs/debug.log.txt`.\n\n## URL rewrite\n - File _.htaccess_\n\n```text\n# Important: AllowOverride All in httpd.conf\n\nRewriteEngine On\nRewriteRule ^svc/(\\w+)$           restql.php?$1           [NC,L]\nRewriteRule ^svc/(\\w+)/revision$  restql.php?$1/revision  [NC,L]\n\norder deny,allow\n\u003cfiles *.*\u003e\n\tdeny from all\n\u003c/files\u003e\n\u003cfilesmatch \\.php$\u003e\n\tallow from all\n\u003c/filesmatch\u003e\n```\n## Service invocation\n - URL with URL rewrite:\n   `\u003cbase_url\u003e/svc/\u003cservice_name\u003e`\n - URL without URL rewrite\n   `\u003cbase_url\u003e/restql.php?\u003cservice_name\u003e`\n - The security token is sent as `Authorization` request header\n - Method `POST`\n - Call arguments are POST-ed as JSON\n - Method `GET`\n - `\u003cservice_name\u003e.config.json` must have `\"arguments\": {}`\n - Request body is ignored\n \n\n#### Service revision check\n - URL with URL rewrite\n `\u003cbase_url\u003e/svc/\u003cservice_name\u003e/revision`\n\n - URL without URL rewrite\n `\u003cbase_url\u003e/restql.php?\u003cservice_name\u003e/revision`\n\n## Service response\n\nJSON with this structure:\n\n```text\n{\n \"status\": true or false,\n \"data\": return data in JSON or error text,\n \"extra\": optional, the return value of postProcess function if any\n}\n```\n## Call example\n\n - URL with URL rewrite: `\u003cbase_url\u003e/svc/demo`\n - URL without URL rewrite: `\u003cbase_url\u003e/restql.php?demo`\n\n#### POST data\n\n```json\n{\n \"lower_limit\": 29,\n \"label\": \"Сарми с лозов лист\"\n}\n```\n\n#### cURL\n```\ncurl -X POST -H 'Authorization: PTn456KSqqU7WhSszSe' -i http://localhost/servicegen/svc/demo --data '{\n \"lower_limit\": 29,\n \"label\": \"Сарми с лозов лист\"\n}'\n```\n\n\u003cdiv style=\"page-break-after: always;\"\u003e\u003c/div\u003e\n\u003cimg src=\"call.png\" style='border: 1px solid black' /\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstefanov-sm%2Frestql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstefanov-sm%2Frestql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstefanov-sm%2Frestql/lists"}