@reldens/storage 0.88.0 → 0.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.claude/driver-methods-complete-analysis.md +1978 -0
  2. package/.claude/test-architecture.md +973 -0
  3. package/CLAUDE.md +132 -3
  4. package/bin/reldens-storage.js +1 -1
  5. package/lib/base-driver.js +8 -0
  6. package/lib/entities-generator.js +1 -3
  7. package/lib/entity-templates/entities-translations.template +3 -0
  8. package/lib/entity-templates/mikro-orm-model.template +2 -1
  9. package/lib/generators/entities-generation.js +12 -1
  10. package/lib/generators/entities-translations-generation.js +40 -4
  11. package/lib/generators/models-generation.js +126 -5
  12. package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
  13. package/lib/mikro-orm/mikro-orm-driver.js +184 -116
  14. package/lib/objection-js/objection-js-driver.js +111 -19
  15. package/lib/prisma/prisma-data-server.js +3 -2
  16. package/lib/prisma/prisma-driver.js +12 -10
  17. package/lib/prisma/prisma-filter-processor.js +193 -146
  18. package/lib/prisma/prisma-metadata-loader.js +0 -4
  19. package/lib/prisma/prisma-relation-resolver.js +292 -267
  20. package/lib/prisma/prisma-type-caster.js +280 -260
  21. package/package.json +15 -6
  22. package/tests/.env.test.example +7 -0
  23. package/tests/fixtures/categories-fixtures.js +164 -0
  24. package/tests/fixtures/expected-entities/entities/test-categories-entity.js +70 -0
  25. package/tests/fixtures/expected-entities/entities/test-products-entity.js +95 -0
  26. package/tests/fixtures/expected-entities/entities/test-reviews-entity.js +84 -0
  27. package/tests/fixtures/expected-entities/entities-config.js +17 -0
  28. package/tests/fixtures/expected-entities/entities-translations.js +53 -0
  29. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
  30. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
  31. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
  32. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
  33. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
  34. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
  35. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
  36. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
  37. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
  38. package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
  39. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
  40. package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
  41. package/tests/fixtures/products-fixtures.js +119 -0
  42. package/tests/fixtures/reviews-fixtures.js +42 -0
  43. package/tests/fixtures/sql/test-schema.sql +59 -0
  44. package/tests/integration/test-drivers.js +257 -0
  45. package/tests/integration/test-nested-filters.js +408 -0
  46. package/tests/integration/test-relations.js +349 -0
  47. package/tests/run-tests.js +213 -0
  48. package/tests/unit/test-drivers.js +89 -0
  49. package/tests/unit/test-entities-generator.js +533 -0
  50. package/tests/unit/test-entity-manager.js +128 -0
  51. package/tests/unit/test-type-mapper.js +232 -0
  52. package/tests/utils/custom-reporter.js +73 -0
  53. package/tests/utils/driver-registry.js +144 -0
  54. package/tests/utils/prisma-subprocess-worker.js +150 -0
  55. package/tests/utils/test-helpers.js +726 -0
  56. package/tests/utils/test-runner.js +63 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ *
3
+ * Reldens - TestReviewsModel
4
+ *
5
+ */
6
+
7
+ const { MikroOrmCore } = require('../../../index');
8
+ const { EntitySchema } = MikroOrmCore;
9
+
10
+ class TestReviewsModel
11
+ {
12
+
13
+ constructor(id, product_id, reviewer_name, reviewer_email, rating, title, comment, is_verified, helpful_count, created_at, updated_at)
14
+ {
15
+ this.id = id;
16
+ this.product_id = product_id;
17
+ this.reviewer_name = reviewer_name;
18
+ this.reviewer_email = reviewer_email;
19
+ this.rating = rating;
20
+ this.title = title;
21
+ this.comment = comment;
22
+ this.is_verified = is_verified;
23
+ this.helpful_count = helpful_count;
24
+ this.created_at = created_at;
25
+ this.updated_at = updated_at;
26
+ }
27
+
28
+ static createByProps(props)
29
+ {
30
+ const {id, product_id, reviewer_name, reviewer_email, rating, title, comment, is_verified, helpful_count, created_at, updated_at} = props;
31
+ return new this(id, product_id, reviewer_name, reviewer_email, rating, title, comment, is_verified, helpful_count, created_at, updated_at);
32
+ }
33
+
34
+ }
35
+
36
+ const schema = new EntitySchema({
37
+ class: TestReviewsModel,
38
+ tableName: 'test_reviews',
39
+ properties: {
40
+ id: { type: 'number', primary: true },
41
+ product_id: { type: 'number', persist: false },
42
+ reviewer_name: { type: 'string' },
43
+ reviewer_email: { type: 'string' },
44
+ rating: { type: 'number' },
45
+ title: { type: 'string', nullable: true },
46
+ comment: { type: 'string', nullable: true },
47
+ is_verified: { type: 'number', nullable: true },
48
+ helpful_count: { type: 'number', nullable: true },
49
+ created_at: { type: 'Date', nullable: true },
50
+ updated_at: { type: 'Date', nullable: true },
51
+ related_test_products: {
52
+ kind: 'm:1',
53
+ entity: 'TestProductsModel',
54
+ joinColumn: 'product_id'
55
+ }
56
+ },
57
+ });
58
+ schema._fkMappings = {
59
+ "product_id": {
60
+ "relationKey": "related_test_products",
61
+ "entityName": "TestProductsModel",
62
+ "referencedColumn": "id",
63
+ "nullable": false
64
+ }
65
+ };
66
+ module.exports = {
67
+ TestReviewsModel,
68
+ entity: TestReviewsModel,
69
+ schema: schema
70
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ *
3
+ * Reldens - Registered Models
4
+ *
5
+ */
6
+
7
+ const { TestCategoriesModel } = require('./test-categories-model');
8
+ const { TestProductsModel } = require('./test-products-model');
9
+ const { TestReviewsModel } = require('./test-reviews-model');
10
+ const { entitiesConfig } = require('../../entities-config');
11
+ const { entitiesTranslations } = require('../../entities-translations');
12
+
13
+ let rawRegisteredEntities = {
14
+ testCategories: TestCategoriesModel,
15
+ testProducts: TestProductsModel,
16
+ testReviews: TestReviewsModel
17
+ };
18
+
19
+ module.exports.rawRegisteredEntities = rawRegisteredEntities;
20
+
21
+ module.exports.entitiesConfig = entitiesConfig;
22
+
23
+ module.exports.entitiesTranslations = entitiesTranslations;
@@ -0,0 +1,33 @@
1
+ /**
2
+ *
3
+ * Reldens - TestCategoriesModel
4
+ *
5
+ */
6
+
7
+ const { ObjectionJsRawModel } = require('../../../index');
8
+
9
+ class TestCategoriesModel extends ObjectionJsRawModel
10
+ {
11
+
12
+ static get tableName()
13
+ {
14
+ return 'test_categories';
15
+ }
16
+
17
+ static get relationMappings()
18
+ {
19
+ const { TestProductsModel } = require('./test-products-model');
20
+ return {
21
+ related_test_products: {
22
+ relation: this.HasManyRelation,
23
+ modelClass: TestProductsModel,
24
+ join: {
25
+ from: this.tableName+'.id',
26
+ to: TestProductsModel.tableName+'.category_id'
27
+ }
28
+ }
29
+ };
30
+ }
31
+ }
32
+
33
+ module.exports.TestCategoriesModel = TestCategoriesModel;
@@ -0,0 +1,42 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductsModel
4
+ *
5
+ */
6
+
7
+ const { ObjectionJsRawModel } = require('../../../index');
8
+
9
+ class TestProductsModel extends ObjectionJsRawModel
10
+ {
11
+
12
+ static get tableName()
13
+ {
14
+ return 'test_products';
15
+ }
16
+
17
+ static get relationMappings()
18
+ {
19
+ const { TestCategoriesModel } = require('./test-categories-model');
20
+ const { TestReviewsModel } = require('./test-reviews-model');
21
+ return {
22
+ related_test_categories: {
23
+ relation: this.BelongsToOneRelation,
24
+ modelClass: TestCategoriesModel,
25
+ join: {
26
+ from: this.tableName+'.category_id',
27
+ to: TestCategoriesModel.tableName+'.id'
28
+ }
29
+ },
30
+ related_test_reviews: {
31
+ relation: this.HasManyRelation,
32
+ modelClass: TestReviewsModel,
33
+ join: {
34
+ from: this.tableName+'.id',
35
+ to: TestReviewsModel.tableName+'.product_id'
36
+ }
37
+ }
38
+ };
39
+ }
40
+ }
41
+
42
+ module.exports.TestProductsModel = TestProductsModel;
@@ -0,0 +1,33 @@
1
+ /**
2
+ *
3
+ * Reldens - TestReviewsModel
4
+ *
5
+ */
6
+
7
+ const { ObjectionJsRawModel } = require('../../../index');
8
+
9
+ class TestReviewsModel extends ObjectionJsRawModel
10
+ {
11
+
12
+ static get tableName()
13
+ {
14
+ return 'test_reviews';
15
+ }
16
+
17
+ static get relationMappings()
18
+ {
19
+ const { TestProductsModel } = require('./test-products-model');
20
+ return {
21
+ related_test_products: {
22
+ relation: this.BelongsToOneRelation,
23
+ modelClass: TestProductsModel,
24
+ join: {
25
+ from: this.tableName+'.product_id',
26
+ to: TestProductsModel.tableName+'.id'
27
+ }
28
+ }
29
+ };
30
+ }
31
+ }
32
+
33
+ module.exports.TestReviewsModel = TestReviewsModel;
@@ -0,0 +1,23 @@
1
+ /**
2
+ *
3
+ * Reldens - Registered Models
4
+ *
5
+ */
6
+
7
+ const { TestCategoriesModel } = require('./test-categories-model');
8
+ const { TestProductsModel } = require('./test-products-model');
9
+ const { TestReviewsModel } = require('./test-reviews-model');
10
+ const { entitiesConfig } = require('../../entities-config');
11
+ const { entitiesTranslations } = require('../../entities-translations');
12
+
13
+ let rawRegisteredEntities = {
14
+ testCategories: TestCategoriesModel,
15
+ testProducts: TestProductsModel,
16
+ testReviews: TestReviewsModel
17
+ };
18
+
19
+ module.exports.rawRegisteredEntities = rawRegisteredEntities;
20
+
21
+ module.exports.entitiesConfig = entitiesConfig;
22
+
23
+ module.exports.entitiesTranslations = entitiesTranslations;
@@ -0,0 +1,43 @@
1
+ /**
2
+ *
3
+ * Reldens - TestCategoriesModel
4
+ *
5
+ */
6
+
7
+ class TestCategoriesModel
8
+ {
9
+
10
+ constructor(id, name, slug, description, is_active, display_order, created_at, updated_at)
11
+ {
12
+ this.id = id;
13
+ this.name = name;
14
+ this.slug = slug;
15
+ this.description = description;
16
+ this.is_active = is_active;
17
+ this.display_order = display_order;
18
+ this.created_at = created_at;
19
+ this.updated_at = updated_at;
20
+ }
21
+
22
+ static get tableName()
23
+ {
24
+ return 'test_categories';
25
+ }
26
+
27
+
28
+ static get relationTypes()
29
+ {
30
+ return {
31
+ test_products: 'many'
32
+ };
33
+ }
34
+
35
+ static get relationMappings()
36
+ {
37
+ return {
38
+ 'related_test_products': 'test_products'
39
+ };
40
+ }
41
+ }
42
+
43
+ module.exports.TestCategoriesModel = TestCategoriesModel;
@@ -0,0 +1,50 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductsModel
4
+ *
5
+ */
6
+
7
+ class TestProductsModel
8
+ {
9
+
10
+ constructor(id, category_id, name, sku, description, price, stock_quantity, is_featured, metadata, tags, status, created_at, updated_at)
11
+ {
12
+ this.id = id;
13
+ this.category_id = category_id;
14
+ this.name = name;
15
+ this.sku = sku;
16
+ this.description = description;
17
+ this.price = price;
18
+ this.stock_quantity = stock_quantity;
19
+ this.is_featured = is_featured;
20
+ this.metadata = metadata;
21
+ this.tags = tags;
22
+ this.status = status;
23
+ this.created_at = created_at;
24
+ this.updated_at = updated_at;
25
+ }
26
+
27
+ static get tableName()
28
+ {
29
+ return 'test_products';
30
+ }
31
+
32
+
33
+ static get relationTypes()
34
+ {
35
+ return {
36
+ test_categories: 'one',
37
+ test_reviews: 'many'
38
+ };
39
+ }
40
+
41
+ static get relationMappings()
42
+ {
43
+ return {
44
+ 'related_test_categories': 'test_categories',
45
+ 'related_test_reviews': 'test_reviews'
46
+ };
47
+ }
48
+ }
49
+
50
+ module.exports.TestProductsModel = TestProductsModel;
@@ -0,0 +1,46 @@
1
+ /**
2
+ *
3
+ * Reldens - TestReviewsModel
4
+ *
5
+ */
6
+
7
+ class TestReviewsModel
8
+ {
9
+
10
+ constructor(id, product_id, reviewer_name, reviewer_email, rating, title, comment, is_verified, helpful_count, created_at, updated_at)
11
+ {
12
+ this.id = id;
13
+ this.product_id = product_id;
14
+ this.reviewer_name = reviewer_name;
15
+ this.reviewer_email = reviewer_email;
16
+ this.rating = rating;
17
+ this.title = title;
18
+ this.comment = comment;
19
+ this.is_verified = is_verified;
20
+ this.helpful_count = helpful_count;
21
+ this.created_at = created_at;
22
+ this.updated_at = updated_at;
23
+ }
24
+
25
+ static get tableName()
26
+ {
27
+ return 'test_reviews';
28
+ }
29
+
30
+
31
+ static get relationTypes()
32
+ {
33
+ return {
34
+ test_products: 'one'
35
+ };
36
+ }
37
+
38
+ static get relationMappings()
39
+ {
40
+ return {
41
+ 'related_test_products': 'test_products'
42
+ };
43
+ }
44
+ }
45
+
46
+ module.exports.TestReviewsModel = TestReviewsModel;
@@ -0,0 +1,119 @@
1
+ /**
2
+ *
3
+ * Reldens - Test Products Fixtures
4
+ * Universal fixtures for all drivers (no driver-specific nesting)
5
+ *
6
+ */
7
+
8
+ module.exports.ProductsFixtures = {
9
+ product_create_json: {
10
+ name: 'Laptop Pro 15',
11
+ description: 'High-performance laptop',
12
+ sku: 'LAPTOP-PRO-15',
13
+ price: 1299.99,
14
+ stock_quantity: 50,
15
+ is_featured: 1,
16
+ metadata: JSON.stringify({warranty: '2 years', color: 'silver'}),
17
+ tags: 'electronics,computers,featured',
18
+ status: 'published'
19
+ },
20
+ product_create_enum: {
21
+ name: 'T-Shirt Classic',
22
+ description: 'Cotton t-shirt',
23
+ sku: 'TSHIRT-CLASSIC',
24
+ price: 24.99,
25
+ stock_quantity: 200,
26
+ is_featured: 0,
27
+ metadata: JSON.stringify({size: 'M', material: 'cotton'}),
28
+ tags: 'clothing,apparel',
29
+ status: 'published'
30
+ },
31
+ product_fk_test: {
32
+ category_id: 9999,
33
+ name: 'Invalid Category Product',
34
+ description: 'Product with invalid category FK',
35
+ sku: 'INVALID-FK-TEST',
36
+ price: 9.99,
37
+ stock_quantity: 1,
38
+ is_featured: 0,
39
+ metadata: JSON.stringify({}),
40
+ tags: 'test',
41
+ status: 'draft'
42
+ },
43
+ product_reviews_crud: {
44
+ id: 2400,
45
+ category_id: 1400,
46
+ name: 'Reviews CRUD Product',
47
+ description: 'Product for reviews CRUD tests',
48
+ sku: 'REVIEWS-CRUD-PROD',
49
+ price: 99.99,
50
+ stock_quantity: 10,
51
+ is_featured: 1,
52
+ metadata: JSON.stringify({test: 'reviews'}),
53
+ tags: 'test,reviews',
54
+ status: 'published'
55
+ },
56
+ product_filters_1: {
57
+ id: 2500,
58
+ category_id: 1500,
59
+ name: 'Filters Product 1',
60
+ description: 'First product for filter tests',
61
+ sku: 'FILTERS-PROD-1',
62
+ price: 49.99,
63
+ stock_quantity: 25,
64
+ is_featured: 1,
65
+ metadata: JSON.stringify({test: 'filters'}),
66
+ tags: 'test,filters',
67
+ status: 'published'
68
+ },
69
+ product_filters_2: {
70
+ id: 2501,
71
+ category_id: 1500,
72
+ name: 'Filters Product 2',
73
+ description: 'Second product for filter tests',
74
+ sku: 'FILTERS-PROD-2',
75
+ price: 79.99,
76
+ stock_quantity: 15,
77
+ is_featured: 0,
78
+ metadata: JSON.stringify({test: 'filters'}),
79
+ tags: 'test,filters',
80
+ status: 'draft'
81
+ },
82
+ product_relations_1: {
83
+ id: 2600,
84
+ category_id: 1600,
85
+ name: 'Relations Product 1',
86
+ description: 'First product for relations tests',
87
+ sku: 'RELATIONS-PROD-1',
88
+ price: 149.99,
89
+ stock_quantity: 30,
90
+ is_featured: 1,
91
+ metadata: JSON.stringify({test: 'relations'}),
92
+ tags: 'test,relations',
93
+ status: 'published'
94
+ },
95
+ product_relations_2: {
96
+ id: 2601,
97
+ category_id: 1601,
98
+ name: 'Relations Product 2',
99
+ description: 'Second product for relations tests',
100
+ sku: 'RELATIONS-PROD-2',
101
+ price: 199.99,
102
+ stock_quantity: 20,
103
+ is_featured: 1,
104
+ metadata: JSON.stringify({test: 'relations'}),
105
+ tags: 'test,relations',
106
+ status: 'published'
107
+ },
108
+ product_create_nested: {
109
+ name: 'Nested Create Product',
110
+ description: 'Product for createWithRelations test',
111
+ sku: 'NESTED-PROD-001',
112
+ price: 99.99,
113
+ stock_quantity: 10,
114
+ is_featured: 0,
115
+ metadata: JSON.stringify({test: 'nested'}),
116
+ tags: 'test,nested',
117
+ status: 'draft'
118
+ }
119
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ *
3
+ * Reldens - Test Reviews Fixtures
4
+ * Universal fixtures for all drivers (no driver-specific nesting)
5
+ *
6
+ */
7
+
8
+ module.exports.ReviewsFixtures = {
9
+ review_crud_1: {
10
+ id: 3400,
11
+ product_id: 2400,
12
+ reviewer_name: 'John Doe',
13
+ reviewer_email: 'john@example.com',
14
+ rating: 5,
15
+ title: 'Excellent product!',
16
+ comment: 'Very satisfied with this purchase',
17
+ is_verified: 1,
18
+ helpful_count: 10
19
+ },
20
+ review_relations_1: {
21
+ id: 3600,
22
+ product_id: 2600,
23
+ reviewer_name: 'Jane Smith',
24
+ reviewer_email: 'jane@example.com',
25
+ rating: 4,
26
+ title: 'Good value',
27
+ comment: 'Works as expected',
28
+ is_verified: 1,
29
+ helpful_count: 5
30
+ },
31
+ review_relations_2: {
32
+ id: 3601,
33
+ product_id: 2601,
34
+ reviewer_name: 'Bob Johnson',
35
+ reviewer_email: 'bob@example.com',
36
+ rating: 5,
37
+ title: 'Great quality',
38
+ comment: 'Highly recommended',
39
+ is_verified: 1,
40
+ helpful_count: 8
41
+ }
42
+ };
@@ -0,0 +1,59 @@
1
+ DROP TABLE IF EXISTS `test_reviews`;
2
+ DROP TABLE IF EXISTS `test_products`;
3
+ DROP TABLE IF EXISTS `test_categories`;
4
+
5
+ CREATE TABLE `test_categories` (
6
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
7
+ `name` VARCHAR(100) NOT NULL,
8
+ `slug` VARCHAR(100) NOT NULL,
9
+ `description` TEXT NULL,
10
+ `is_active` TINYINT(1) NOT NULL DEFAULT 1,
11
+ `display_order` INT NOT NULL DEFAULT 0,
12
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
13
+ `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
14
+ PRIMARY KEY (`id`),
15
+ UNIQUE KEY `slug` (`slug`),
16
+ KEY `is_active` (`is_active`),
17
+ KEY `display_order` (`display_order`)
18
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
19
+
20
+ CREATE TABLE `test_products` (
21
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
22
+ `category_id` INT UNSIGNED NOT NULL,
23
+ `name` VARCHAR(200) NOT NULL,
24
+ `sku` VARCHAR(50) NOT NULL,
25
+ `description` TEXT NULL,
26
+ `price` DECIMAL(10,2) NOT NULL,
27
+ `stock_quantity` INT NOT NULL DEFAULT 0,
28
+ `is_featured` TINYINT(1) NOT NULL DEFAULT 0,
29
+ `metadata` JSON NULL,
30
+ `tags` VARCHAR(255) NULL,
31
+ `status` ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
32
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
33
+ `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
34
+ PRIMARY KEY (`id`),
35
+ UNIQUE KEY `sku` (`sku`),
36
+ KEY `category_id` (`category_id`),
37
+ KEY `status` (`status`),
38
+ KEY `is_featured` (`is_featured`),
39
+ CONSTRAINT `fk_products_category` FOREIGN KEY (`category_id`) REFERENCES `test_categories` (`id`) ON DELETE CASCADE
40
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
41
+
42
+ CREATE TABLE `test_reviews` (
43
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
44
+ `product_id` INT UNSIGNED NOT NULL,
45
+ `reviewer_name` VARCHAR(100) NOT NULL,
46
+ `reviewer_email` VARCHAR(255) NOT NULL,
47
+ `rating` TINYINT UNSIGNED NOT NULL,
48
+ `title` VARCHAR(200) NULL,
49
+ `comment` TEXT NULL,
50
+ `is_verified` TINYINT(1) NOT NULL DEFAULT 0,
51
+ `helpful_count` INT NOT NULL DEFAULT 0,
52
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
53
+ `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
54
+ PRIMARY KEY (`id`),
55
+ KEY `product_id` (`product_id`),
56
+ KEY `rating` (`rating`),
57
+ KEY `is_verified` (`is_verified`),
58
+ CONSTRAINT `fk_reviews_product` FOREIGN KEY (`product_id`) REFERENCES `test_products` (`id`) ON DELETE CASCADE
59
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;