1
+ <?php
2
+
3
+ namespace Rcalicdan \FiberAsync \QueryBuilder ;
4
+
5
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryBuilderCoreTrait ;
6
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryConditionsTrait ;
7
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryJoinTrait ;
8
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryGroupingTrait ;
9
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryAdvancedConditionsTrait ;
10
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \QueryDebugTrait ;
11
+ use Rcalicdan \FiberAsync \QueryBuilder \Traits \SqlBuilderTrait ;
12
+
13
+ /**
14
+ * Abstract PostgreSQL query builder base with optimizations.
15
+ */
16
+ abstract class PostgresQueryBuilderBase
17
+ {
18
+ use QueryBuilderCoreTrait,
19
+ QueryConditionsTrait,
20
+ QueryJoinTrait,
21
+ QueryGroupingTrait,
22
+ QueryAdvancedConditionsTrait,
23
+ SqlBuilderTrait,
24
+ QueryDebugTrait;
25
+
26
+ /**
27
+ * @var int Parameter counter for PostgreSQL numbered parameters
28
+ */
29
+ protected int $ parameterCount = 0 ;
30
+
31
+ /**
32
+ * Generate PostgreSQL parameter placeholder ($1, $2, $3, etc.)
33
+ */
34
+ protected function getPlaceholder (): string
35
+ {
36
+ return '$ ' . (++$ this ->parameterCount );
37
+ }
38
+
39
+ /**
40
+ * Reset parameter counter for new query
41
+ */
42
+ protected function resetParameterCount (): void
43
+ {
44
+ $ this ->parameterCount = 0 ;
45
+ }
46
+
47
+ /**
48
+ * PostgreSQL ILIKE for case-insensitive matching
49
+ */
50
+ public function ilike (string $ column , string $ value , string $ side = 'both ' ): static
51
+ {
52
+ $ instance = clone $ this ;
53
+ $ placeholder = $ instance ->getPlaceholder ();
54
+ $ instance ->where [] = "{$ column } ILIKE {$ placeholder }" ;
55
+
56
+ $ likeValue = match ($ side ) {
57
+ 'before ' => "% {$ value }" ,
58
+ 'after ' => "{$ value }% " ,
59
+ 'both ' => "% {$ value }% " ,
60
+ default => $ value
61
+ };
62
+
63
+ $ instance ->bindings ['where ' ][] = $ likeValue ;
64
+ return $ instance ;
65
+ }
66
+
67
+ /**
68
+ * PostgreSQL JSON field access
69
+ */
70
+ public function whereJson (string $ column , string $ path , mixed $ value ): static
71
+ {
72
+ $ instance = clone $ this ;
73
+ $ placeholder = $ instance ->getPlaceholder ();
74
+ $ instance ->where [] = "{$ column }->' {$ path }' = {$ placeholder }" ;
75
+ $ instance ->bindings ['where ' ][] = $ value ;
76
+ return $ instance ;
77
+ }
78
+
79
+ /**
80
+ * Override buildSelectQuery to reset parameter count and optimize for PostgreSQL
81
+ */
82
+ protected function buildSelectQuery (): string
83
+ {
84
+ $ this ->resetParameterCount ();
85
+
86
+ $ sql = 'SELECT ' . implode (', ' , $ this ->select );
87
+ $ sql .= ' FROM ' . $ this ->table ;
88
+
89
+ foreach ($ this ->joins as $ join ) {
90
+ if ($ join ['type ' ] === 'CROSS ' ) {
91
+ $ sql .= " CROSS JOIN {$ join ['table ' ]}" ;
92
+ } else {
93
+ $ sql .= " {$ join ['type ' ]} JOIN {$ join ['table ' ]} ON {$ join ['condition ' ]}" ;
94
+ }
95
+ }
96
+
97
+ $ whereSql = $ this ->buildWhereClause ();
98
+ if ($ whereSql !== '' ) {
99
+ $ sql .= ' WHERE ' . $ whereSql ;
100
+ }
101
+
102
+ if ($ this ->groupBy !== []) {
103
+ $ sql .= ' GROUP BY ' . implode (', ' , $ this ->groupBy );
104
+ }
105
+
106
+ if ($ this ->having !== []) {
107
+ $ sql .= ' HAVING ' . implode (' AND ' , $ this ->having );
108
+ }
109
+
110
+ if ($ this ->orderBy !== []) {
111
+ $ sql .= ' ORDER BY ' . implode (', ' , $ this ->orderBy );
112
+ }
113
+
114
+ if ($ this ->limit !== null ) {
115
+ $ sql .= ' LIMIT ' . $ this ->limit ;
116
+ if ($ this ->offset !== null ) {
117
+ $ sql .= ' OFFSET ' . $ this ->offset ;
118
+ }
119
+ }
120
+
121
+ return $ sql ;
122
+ }
123
+
124
+ /**
125
+ * Override buildCountQuery for PostgreSQL
126
+ */
127
+ protected function buildCountQuery (string $ column = '* ' ): string
128
+ {
129
+ $ this ->resetParameterCount ();
130
+
131
+ $ sql = "SELECT COUNT( {$ column }) FROM " . $ this ->table ;
132
+
133
+ foreach ($ this ->joins as $ join ) {
134
+ if ($ join ['type ' ] === 'CROSS ' ) {
135
+ $ sql .= " CROSS JOIN {$ join ['table ' ]}" ;
136
+ } else {
137
+ $ sql .= " {$ join ['type ' ]} JOIN {$ join ['table ' ]} ON {$ join ['condition ' ]}" ;
138
+ }
139
+ }
140
+
141
+ $ whereSql = $ this ->buildWhereClause ();
142
+ if ($ whereSql !== '' ) {
143
+ $ sql .= ' WHERE ' . $ whereSql ;
144
+ }
145
+
146
+ if ($ this ->groupBy !== []) {
147
+ $ sql .= ' GROUP BY ' . implode (', ' , $ this ->groupBy );
148
+ }
149
+
150
+ if ($ this ->having !== []) {
151
+ $ sql .= ' HAVING ' . implode (' AND ' , $ this ->having );
152
+ }
153
+
154
+ return $ sql ;
155
+ }
156
+
157
+ /**
158
+ * Override buildInsertQuery for PostgreSQL
159
+ */
160
+ protected function buildInsertQuery (array $ data ): string
161
+ {
162
+ $ this ->resetParameterCount ();
163
+
164
+ $ columns = implode (', ' , array_keys ($ data ));
165
+ $ placeholders = [];
166
+
167
+ foreach ($ data as $ value ) {
168
+ $ placeholders [] = $ this ->getPlaceholder ();
169
+ }
170
+
171
+ $ placeholderString = implode (', ' , $ placeholders );
172
+ return "INSERT INTO {$ this ->table } ( {$ columns }) VALUES ( {$ placeholderString }) " ;
173
+ }
174
+
175
+ /**
176
+ * Override buildInsertBatchQuery for PostgreSQL
177
+ */
178
+ protected function buildInsertBatchQuery (array $ data ): string
179
+ {
180
+ $ this ->resetParameterCount ();
181
+
182
+ if (empty ($ data ) || !is_array ($ data [0 ])) {
183
+ throw new \InvalidArgumentException ('Invalid data format for batch insert ' );
184
+ }
185
+
186
+ $ firstRow = $ data [0 ];
187
+ $ columns = implode (', ' , array_keys ($ firstRow ));
188
+
189
+ $ valueGroups = [];
190
+ foreach ($ data as $ row ) {
191
+ $ rowPlaceholders = [];
192
+ foreach ($ row as $ value ) {
193
+ $ rowPlaceholders [] = $ this ->getPlaceholder ();
194
+ }
195
+ $ valueGroups [] = '( ' . implode (', ' , $ rowPlaceholders ) . ') ' ;
196
+ }
197
+
198
+ $ allPlaceholders = implode (', ' , $ valueGroups );
199
+ return "INSERT INTO {$ this ->table } ( {$ columns }) VALUES {$ allPlaceholders }" ;
200
+ }
201
+
202
+ /**
203
+ * Override buildUpdateQuery for PostgreSQL
204
+ */
205
+ protected function buildUpdateQuery (array $ data ): string
206
+ {
207
+ $ this ->resetParameterCount ();
208
+
209
+ $ setClauses = [];
210
+ foreach (array_keys ($ data ) as $ column ) {
211
+ $ setClauses [] = "{$ column } = " . $ this ->getPlaceholder ();
212
+ }
213
+
214
+ $ sql = "UPDATE {$ this ->table } SET " . implode (', ' , $ setClauses );
215
+
216
+ $ whereSql = $ this ->buildWhereClause ();
217
+ if ($ whereSql !== '' ) {
218
+ $ sql .= ' WHERE ' . $ whereSql ;
219
+ }
220
+
221
+ return $ sql ;
222
+ }
223
+
224
+ /**
225
+ * Override buildDeleteQuery for PostgreSQL
226
+ */
227
+ protected function buildDeleteQuery (): string
228
+ {
229
+ $ this ->resetParameterCount ();
230
+
231
+ $ sql = "DELETE FROM {$ this ->table }" ;
232
+
233
+ $ whereSql = $ this ->buildWhereClause ();
234
+ if ($ whereSql !== '' ) {
235
+ $ sql .= ' WHERE ' . $ whereSql ;
236
+ }
237
+
238
+ return $ sql ;
239
+ }
240
+
241
+ /**
242
+ * Build INSERT with RETURNING clause for PostgreSQL
243
+ */
244
+ protected function buildInsertReturningQuery (array $ data , string $ returning = 'id ' ): string
245
+ {
246
+ return $ this ->buildInsertQuery ($ data ) . " RETURNING {$ returning }" ;
247
+ }
248
+ }
0 commit comments