Browse Source

Support reading views

Maurits van der Schee 3 years ago
parent
commit
53279412b2

+ 47
- 24
api.php View File

@@ -3879,23 +3879,43 @@ namespace Tqdev\PhpCrudApi\Column\Reflection {
3879 3879
                 $columns[$column->getName()] = $column;
3880 3880
             }
3881 3881
             // set primary key
3882
-            $columnNames = $reflection->getTablePrimaryKeys($name);
3883
-            if (count($columnNames) == 1) {
3884
-                $columnName = $columnNames[0];
3885
-                if (isset($columns[$columnName])) {
3886
-                    $pk = $columns[$columnName];
3887
-                    $pk->setPk(true);
3882
+            $columnName = false;
3883
+            if ($type == 'view') {
3884
+                $columnName = 'id';
3885
+            } else {
3886
+                $columnNames = $reflection->getTablePrimaryKeys($name);
3887
+                if (count($columnNames) == 1) {
3888
+                    $columnName = $columnNames[0];
3888 3889
                 }
3889 3890
             }
3891
+            if ($columnName && isset($columns[$columnName])) {
3892
+                $pk = $columns[$columnName];
3893
+                $pk->setPk(true);
3894
+            }
3890 3895
             // set foreign keys
3891
-            $fks = $reflection->getTableForeignKeys($name);
3892
-            foreach ($fks as $columnName => $table) {
3893
-                $columns[$columnName]->setFk($table);
3896
+            if ($type == 'view') {
3897
+                $tables = $reflection->getTables();
3898
+                foreach ($columns as $columnName => $column) {
3899
+                    if (substr($columnName, -3) == '_id') {
3900
+                        foreach ($tables as $table) {
3901
+                            $tableName = $table['TABLE_NAME'];
3902
+                            $suffix = $tableName . '_id';
3903
+                            if (substr($columnName, -1 * strlen($suffix)) == $suffix) {
3904
+                                $column->setFk($tableName);
3905
+                            }
3906
+                        }
3907
+                    }
3908
+                }
3909
+            } else {
3910
+                $fks = $reflection->getTableForeignKeys($name);
3911
+                foreach ($fks as $columnName => $table) {
3912
+                    $columns[$columnName]->setFk($table);
3913
+                }
3894 3914
             }
3895 3915
             return new ReflectedTable($name, $type, array_values($columns));
3896 3916
         }
3897 3917
 
3898
-        public static function fromJson(/* object */$json): ReflectedTable
3918
+        public static function fromJson( /* object */$json): ReflectedTable
3899 3919
         {
3900 3920
             $name = $json->name;
3901 3921
             $type = isset($json->type) ? $json->type : 'table';
@@ -4590,9 +4610,6 @@ namespace Tqdev\PhpCrudApi\Controller {
4590 4610
             if (!$this->service->hasTable($table)) {
4591 4611
                 return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
4592 4612
             }
4593
-            if ($this->service->getType($table) != 'table') {
4594
-                return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
4595
-            }
4596 4613
             $id = RequestUtils::getPathSegment($request, 3);
4597 4614
             $params = RequestUtils::getParams($request);
4598 4615
             if (strpos($id, ',') !== false) {
@@ -6994,7 +7011,9 @@ namespace Tqdev\PhpCrudApi\Middleware\Router {
6994 7011
                         return substr($fullPath, 0, -1 * strlen($path));
6995 7012
                     }
6996 7013
                 }
6997
-                return $fullPath;
7014
+                if ('/' . basename(__FILE__) == $fullPath) {
7015
+                    return $fullPath;
7016
+                }
6998 7017
             }
6999 7018
             return '/';
7000 7019
         }
@@ -9045,9 +9064,9 @@ namespace Tqdev\PhpCrudApi\OpenApi {
9045 9064
 namespace Tqdev\PhpCrudApi\OpenApi {
9046 9065
 
9047 9066
     use Tqdev\PhpCrudApi\Column\ReflectionService;
9067
+    use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
9048 9068
     use Tqdev\PhpCrudApi\Middleware\Communication\VariableStore;
9049 9069
     use Tqdev\PhpCrudApi\OpenApi\OpenApiDefinition;
9050
-    use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
9051 9070
 
9052 9071
     class OpenApiRecordsBuilder
9053 9072
     {
@@ -9065,16 +9084,16 @@ namespace Tqdev\PhpCrudApi\OpenApi {
9065 9084
             'integer' => ['type' => 'integer', 'format' => 'int32'],
9066 9085
             'bigint' => ['type' => 'integer', 'format' => 'int64'],
9067 9086
             'varchar' => ['type' => 'string'],
9068
-            'clob' => ['type' => 'string', 'format' => 'large-string'],   //custom format
9087
+            'clob' => ['type' => 'string', 'format' => 'large-string'], //custom format
9069 9088
             'varbinary' => ['type' => 'string', 'format' => 'byte'],
9070
-            'blob' => ['type' => 'string', 'format' => 'large-byte'],     //custom format
9071
-            'decimal' => ['type' => 'string', 'format' => 'decimal'],     //custom format
9089
+            'blob' => ['type' => 'string', 'format' => 'large-byte'], //custom format
9090
+            'decimal' => ['type' => 'string', 'format' => 'decimal'], //custom format
9072 9091
             'float' => ['type' => 'number', 'format' => 'float'],
9073 9092
             'double' => ['type' => 'number', 'format' => 'double'],
9074 9093
             'date' => ['type' => 'string', 'format' => 'date'],
9075
-            'time' => ['type' => 'string', 'format' => 'time'],           //custom format
9094
+            'time' => ['type' => 'string', 'format' => 'time'], //custom format
9076 9095
             'timestamp' => ['type' => 'string', 'format' => 'date-time'],
9077
-            'geometry' => ['type' => 'string', 'format' => 'geometry'],   //custom format
9096
+            'geometry' => ['type' => 'string', 'format' => 'geometry'], //custom format
9078 9097
             'boolean' => ['type' => 'boolean'],
9079 9098
         ];
9080 9099
 
@@ -9264,7 +9283,10 @@ namespace Tqdev\PhpCrudApi\OpenApi {
9264 9283
                 if (!$pkName && $operation != 'list') {
9265 9284
                     continue;
9266 9285
                 }
9267
-                if ($type != 'table' && $operation != 'list') {
9286
+                if ($type == 'view' && !in_array($operation, array('read', 'list'))) {
9287
+                    continue;
9288
+                }
9289
+                if ($type == 'view' && !$pkName && $operation == 'read') {
9268 9290
                     continue;
9269 9291
                 }
9270 9292
                 if ($operation == 'delete') {
@@ -11170,6 +11192,8 @@ namespace Tqdev\PhpCrudApi {
11170 11192
     class ResponseFactory
11171 11193
     {
11172 11194
         const OK = 200;
11195
+        const MOVED_PERMANENTLY = 301;
11196
+        const FOUND = 302;
11173 11197
         const UNAUTHORIZED = 401;
11174 11198
         const FORBIDDEN = 403;
11175 11199
         const NOT_FOUND = 404;
@@ -11185,8 +11209,7 @@ namespace Tqdev\PhpCrudApi {
11185 11209
 
11186 11210
         public static function fromCsv(int $status, string $csv): ResponseInterface
11187 11211
         {
11188
-            $response = self::from($status, 'text/csv', $csv);
11189
-            return $response->withHeader('Content-Type', 'text/csv');
11212
+            return self::from($status, 'text/csv', $csv);
11190 11213
         }
11191 11214
 
11192 11215
         public static function fromHtml(int $status, string $html): ResponseInterface
@@ -11200,7 +11223,7 @@ namespace Tqdev\PhpCrudApi {
11200 11223
             return self::from($status, 'application/json', $content);
11201 11224
         }
11202 11225
 
11203
-        private static function from(int $status, string $contentType, string $content): ResponseInterface
11226
+        public static function from(int $status, string $contentType, string $content): ResponseInterface
11204 11227
         {
11205 11228
             $psr17Factory = new Psr17Factory();
11206 11229
             $response = $psr17Factory->createResponse($status);

+ 1
- 1
tests/fixtures/blog_mysql.sql View File

@@ -130,7 +130,7 @@ INSERT INTO `events` (`name`, `datetime`, `visitors`) VALUES
130 130
 ('Launch', '2016-01-01 13:01:01', 0);
131 131
 
132 132
 DROP VIEW IF EXISTS `tag_usage`;
133
-CREATE VIEW `tag_usage` AS select `name`, count(`name`) AS `count` from `tags`, `post_tags` where `tags`.`id` = `post_tags`.`tag_id` group by `name` order by `count` desc, `name`;
133
+CREATE VIEW `tag_usage` AS select `tags`.`id` as `id`, `name`, count(`name`) AS `count` from `tags`, `post_tags` where `tags`.`id` = `post_tags`.`tag_id` group by `tags`.`id`, `name` order by `count` desc, `name`;
134 134
 
135 135
 DROP TABLE IF EXISTS `products`;
136 136
 CREATE TABLE `products` (

+ 1
- 1
tests/fixtures/blog_pgsql.sql View File

@@ -127,7 +127,7 @@ CREATE TABLE events (
127 127
 -- Name: tag_usage; Type: VIEW; Schema: public; Owner: postgres; Tablespace:
128 128
 --
129 129
 
130
-CREATE VIEW "tag_usage" AS select "name", count("name") AS "count" from "tags", "post_tags" where "tags"."id" = "post_tags"."tag_id" group by "name" order by "count" desc, "name";
130
+CREATE VIEW "tag_usage" AS select "tags"."id" as "id", "name", count("name") AS "count" from "tags", "post_tags" where "tags"."id" = "post_tags"."tag_id" group by "tags"."id", "name" order by "count" desc, "name";
131 131
 
132 132
 --
133 133
 -- Name: products; Type: TABLE; Schema: public; Owner: postgres; Tablespace:

+ 1
- 1
tests/fixtures/blog_sqlite.sql View File

@@ -115,7 +115,7 @@ CREATE TABLE "events" (
115 115
 INSERT INTO "events" ("id", "name", "datetime", "visitors") VALUES (1, 'Launch', '2016-01-01 13:01:01', 0);
116 116
 
117 117
 DROP VIEW IF EXISTS "tag_usage";
118
-CREATE VIEW "tag_usage" AS select "name", count("name") AS "count" from "tags", "post_tags" where "tags"."id" = "post_tags"."tag_id" group by "name" order by "count" desc, "name";
118
+CREATE VIEW "tag_usage" AS select "tags"."id" as "id", "name", count("name") AS "count" from "tags", "post_tags" where "tags"."id" = "post_tags"."tag_id" group by "tags"."id", "name" order by "count" desc, "name";
119 119
 
120 120
 DROP TABLE IF EXISTS "products";
121 121
 CREATE TABLE "products" (

+ 1
- 1
tests/fixtures/blog_sqlsrv.sql View File

@@ -246,7 +246,7 @@ GO
246 246
 
247 247
 CREATE VIEW [tag_usage]
248 248
 AS
249
-SELECT top 100 PERCENT name, COUNT_BIG(name) AS [count] FROM tags, post_tags WHERE tags.id = post_tags.tag_id GROUP BY name ORDER BY [count] DESC, name
249
+SELECT top 100 PERCENT tags.id as id, name, COUNT_BIG(name) AS [count] FROM tags, post_tags WHERE tags.id = post_tags.tag_id GROUP BY tags.id, name ORDER BY [count] DESC, name
250 250
 GO
251 251
 
252 252
 DROP SEQUENCE IF EXISTS [products_id_seq]

+ 12
- 4
tests/functional/001_records/075_list_tag_usage.log View File

@@ -3,14 +3,22 @@ GET /records/tag_usage
3 3
 ===
4 4
 200
5 5
 Content-Type: application/json; charset=utf-8
6
-Content-Length: 71
6
+Content-Length: 85
7 7
 
8
-{"records":[{"name":"funny","count":2},{"name":"important","count":2}]}
8
+{"records":[{"id":1,"name":"funny","count":2},{"id":2,"name":"important","count":2}]}
9 9
 ===
10 10
 GET /records/tag_usage/1
11 11
 ===
12
+200
13
+Content-Type: application/json; charset=utf-8
14
+Content-Length: 33
15
+
16
+{"id":1,"name":"funny","count":2}
17
+===
18
+DELETE /records/tag_usage/1
19
+===
12 20
 405
13 21
 Content-Type: application/json; charset=utf-8
14
-Content-Length: 56
22
+Content-Length: 58
15 23
 
16
-{"code":1015,"message":"Operation 'read' not supported"}
24
+{"code":1015,"message":"Operation 'delete' not supported"}

+ 2
- 2
tests/functional/003_columns/001_get_database.log View File

@@ -4,6 +4,6 @@ GET /columns
4 4
 ===
5 5
 200
6 6
 Content-Type: application/json; charset=utf-8
7
-Content-Length: 2799
7
+Content-Length: 2840
8 8
 
9
-{"tables":[{"name":"barcodes","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"product_id","type":"integer","fk":"products"},{"name":"hex","type":"varchar","length":255},{"name":"bin","type":"blob"},{"name":"ip_address","type":"varchar","length":15,"nullable":true}]},{"name":"categories","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"icon","type":"blob","nullable":true}]},{"name":"comments","type":"table","columns":[{"name":"id","type":"bigint","pk":true},{"name":"post_id","type":"integer","fk":"posts"},{"name":"message","type":"varchar","length":255},{"name":"category_id","type":"integer","fk":"categories"}]},{"name":"countries","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"shape","type":"geometry"}]},{"name":"events","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"datetime","type":"timestamp","nullable":true},{"name":"visitors","type":"bigint","nullable":true}]},{"name":"kunsthåndværk","type":"table","columns":[{"name":"id","type":"varchar","length":36,"pk":true},{"name":"Umlauts ä_ö_ü-COUNT","type":"integer"},{"name":"user_id","type":"integer","fk":"users"},{"name":"invisible_id","type":"varchar","length":36,"nullable":true,"fk":"invisibles"}]},{"name":"nopk","type":"table","columns":[{"name":"id","type":"varchar","length":36}]},{"name":"post_tags","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"post_id","type":"integer","fk":"posts"},{"name":"tag_id","type":"integer","fk":"tags"}]},{"name":"posts","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"user_id","type":"integer","fk":"users"},{"name":"category_id","type":"integer","fk":"categories"},{"name":"content","type":"varchar","length":255}]},{"name":"products","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"price","type":"decimal","precision":10,"scale":2},{"name":"properties","type":"clob"},{"name":"created_at","type":"timestamp"},{"name":"deleted_at","type":"timestamp","nullable":true}]},{"name":"tag_usage","type":"view","columns":[{"name":"name","type":"varchar","length":255},{"name":"count","type":"bigint"}]},{"name":"tags","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"is_important","type":"boolean"}]},{"name":"users","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"username","type":"varchar","length":255},{"name":"password","type":"varchar","length":255},{"name":"location","type":"geometry","nullable":true}]}]}
9
+{"tables":[{"name":"barcodes","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"product_id","type":"integer","fk":"products"},{"name":"hex","type":"varchar","length":255},{"name":"bin","type":"blob"},{"name":"ip_address","type":"varchar","length":15,"nullable":true}]},{"name":"categories","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"icon","type":"blob","nullable":true}]},{"name":"comments","type":"table","columns":[{"name":"id","type":"bigint","pk":true},{"name":"post_id","type":"integer","fk":"posts"},{"name":"message","type":"varchar","length":255},{"name":"category_id","type":"integer","fk":"categories"}]},{"name":"countries","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"shape","type":"geometry"}]},{"name":"events","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"datetime","type":"timestamp","nullable":true},{"name":"visitors","type":"bigint","nullable":true}]},{"name":"kunsthåndværk","type":"table","columns":[{"name":"id","type":"varchar","length":36,"pk":true},{"name":"Umlauts ä_ö_ü-COUNT","type":"integer"},{"name":"user_id","type":"integer","fk":"users"},{"name":"invisible_id","type":"varchar","length":36,"nullable":true,"fk":"invisibles"}]},{"name":"nopk","type":"table","columns":[{"name":"id","type":"varchar","length":36}]},{"name":"post_tags","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"post_id","type":"integer","fk":"posts"},{"name":"tag_id","type":"integer","fk":"tags"}]},{"name":"posts","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"user_id","type":"integer","fk":"users"},{"name":"category_id","type":"integer","fk":"categories"},{"name":"content","type":"varchar","length":255}]},{"name":"products","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"price","type":"decimal","precision":10,"scale":2},{"name":"properties","type":"clob"},{"name":"created_at","type":"timestamp"},{"name":"deleted_at","type":"timestamp","nullable":true}]},{"name":"tag_usage","type":"view","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"count","type":"bigint"}]},{"name":"tags","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"name","type":"varchar","length":255},{"name":"is_important","type":"boolean"}]},{"name":"users","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"username","type":"varchar","length":255},{"name":"password","type":"varchar","length":255},{"name":"location","type":"geometry","nullable":true}]}]}

Loading…
Cancel
Save