├── lib
├── pdoonecli
├── IPdoOneCache.php
├── PdoOneEncryption.php
├── ext
│ ├── PdoOne_TestMockup.php
│ ├── PdoOne_IExt.php
│ ├── PdoOne_Sqlite.php
│ ├── PdoOne_Mysql.php
│ ├── PdoOne_Sqlsrv.php
│ ├── PdoOne_Oci.php
│ └── PdoOne_Pgsql.php
└── PdoOneCli.php
├── LICENSE
└── composer.json
/lib/pdoonecli:
--------------------------------------------------------------------------------
1 | =7.4",
33 | "ext-json": "*",
34 | "eftec/clione": "^1.33",
35 | "ext-pdo": "*",
36 | "ext-readline": "*",
37 | "eftec/messagecontainer": "^2.9"
38 | },
39 | "bin": [
40 | "lib/pdoonecli"
41 | ],
42 | "suggest": {
43 | "eftec/pdooneorm": "The ORM extension for PdoOne",
44 | "eftec/validationone": "For keeping and storing the messages"
45 | },
46 | "require-dev": {
47 | "phpunit/phpunit": "^8.5.36"
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/lib/IPdoOneCache.php:
--------------------------------------------------------------------------------
1 |
22 | *
23 | * @param string $uid The unique id. It is generated by sha256 based in the query, parameters, type of
24 | * query and method.
25 | * @param string|string[] $family [optional] It is the family or group of the cache. It could be used to invalidate
26 | * the whole group. For example, to invalidate all the cache related with a table.
27 | * @param mixed|null $data The data to store
28 | * @param null|bool|int $ttl If null then the cache never expires.
29 | * If false then we don't use cache.
30 | * If int then it is the duration of the cache (in seconds)
31 | *
32 | * @return void.
33 | * @noinspection ReturnTypeCanBeDeclaredInspection
34 | */
35 | function setCache($uid, $family = '', $data = null, $ttl = null);
36 |
37 | /**
38 | * Invalidate a single cache or a list of cache based in a single uid or in a family/group of cache.
39 | *
40 | * @param string|string[] $uid The unique id. It is generated by sha256 based in the query, parameters, type of
41 | * query and method.
42 | * @param string|string[] $family [optional] It is the family or group of the cache. It could be used to invalidate
43 | * the whole group. For example, to invalidate all the cache related with a table.
44 | *
45 | * @return mixed
46 | */
47 | function invalidateCache($uid = '', $family = '');
48 | }
49 |
--------------------------------------------------------------------------------
/lib/PdoOneEncryption.php:
--------------------------------------------------------------------------------
1 |
24 | /** @var bool Encryption enabled */
25 | public bool $encEnabled = false;
26 | /**
27 | * @var string=['sha256','sha512','md5'][$i]
28 | * @see https://www.php.net/manual/en/function.hash-algos.php
29 | */
30 | public string $hashType = 'sha256';
31 | /**
32 | * @var string Encryption password.
33 | * If the method is INTEGER, then the password must be an integer
34 | */
35 | public string $encPassword = '';
36 | /** @var string Encryption salt */
37 | public string $encSalt = '';
38 | /**
39 | * @var bool If iv is true then it is generated randomly, otherwise is it generated via md5
40 | * If true, then the encrypted value is always different (but the decryption yields the same value).
41 | * If false, then the value encrypted is the same for the same value.
42 | * Set to false if you want a deterministic value (it always returns the same value)
43 | */
44 | public bool $iv = true;
45 | /**
46 | * @var string
Encryption method, example AES-256-CTR (two ways).
47 | * If the method is SIMPLE (two ways) then it's uses a simple conversion (short generated value)
48 | * If the method is INTEGER (two was) then it's uses another simple conversion (returns an integer)
49 | * @see http://php.net/manual/en/function.openssl-get-cipher-methods.php
50 | */
51 | public string $encMethod = '';
52 |
53 | /**
54 | * PdoOneEncryption constructor.
55 | * @param string $encPassword
56 | * @param string|null $encSalt
57 | * @param bool $iv If iv is true then it is generated randomly (not deterministically)
58 | * otherwise, is it generated via md5
59 | * @param string $encMethod Example : AES-128-CTR @see
60 | * http://php.net/manual/en/function.openssl-get-cipher-methods.php
61 | */
62 | public function __construct(string $encPassword,?string $encSalt = null,bool $iv = true,string $encMethod = 'AES-256-CTR')
63 | {
64 | $this->encPassword = $encPassword;
65 | $this->encSalt = $encSalt ?? $encPassword; // if null then it uses the same password
66 | $this->iv = $iv;
67 | $this->encMethod = $encMethod;
68 | }
69 | //
70 |
71 |
72 | /**
73 | * It is a two-way decryption
74 | * @param mixed $data
75 | * @return bool|string
76 | */
77 | public function decrypt($data)
78 | {
79 | if (!$this->encEnabled || $data === null) {
80 | return $data;
81 | } // no encryption
82 | switch ($this->encMethod) {
83 | case 'SIMPLE':
84 | return $this->decryptSimple($data);
85 | case 'INTEGER':
86 | return $this->decryptInteger($data);
87 | }
88 | $data = base64_decode(str_replace(array('-', '_'), array('+', '/'), $data));
89 | $iv_strlen = 2 * openssl_cipher_iv_length($this->encMethod);
90 | if (preg_match('/^(.{' . $iv_strlen . '})(.+)$/', $data, $regs)) {
91 | try {
92 | [, $iv, $crypted_string] = $regs;
93 | $decrypted_string = openssl_decrypt($crypted_string, $this->encMethod, $this->encPassword, 0, hex2bin($iv));
94 | $result = substr($decrypted_string, strlen($this->encSalt));
95 | if (strlen($result) > 2 && $result[1] === ':') {
96 | /** @noinspection UnserializeExploitsInspection */
97 | $resultfinal = @unserialize($result); // we try to unserialize, if fails, then we keep the current value
98 | $result = $resultfinal === false ? $result : $resultfinal;
99 | }
100 | return $result;
101 | } catch (Exception $ex) {
102 | return false;
103 | }
104 | } else {
105 | return false;
106 | }
107 | }
108 |
109 | /**
110 | * It is a two-way encryption. The result is htlml/link friendly.
111 | * @param mixed $data For the method simple, it could be a simple value (string,int,etc.)
112 | * For the method integer, it must be an integer
113 | * For other methods, it could be any value. If it is an object or array, then it is
114 | * serialized
115 | * @return string|int|false Returns a string with the value encrypted
116 | */
117 | public function encrypt($data)
118 | {
119 | if (!$this->encEnabled) {
120 | return $data;
121 | } // no encryption
122 | switch ($this->encMethod) {
123 | case 'SIMPLE':
124 | return $this->encryptSimple($data);
125 | case 'INTEGER':
126 | return $this->encryptInteger($data);
127 | }
128 | if (is_array($data) || is_object($data)) {
129 | $data = serialize($data);
130 | }
131 | if ($this->iv) {
132 | $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->encMethod));
133 | } else {
134 | $iv = substr(md5($data, true), 0, openssl_cipher_iv_length($this->encMethod));
135 | }
136 | $encrypted_string = bin2hex($iv) . openssl_encrypt($this->encSalt . $data, $this->encMethod
137 | , $this->encPassword, 0, $iv);
138 | return str_replace(array('+', '/'), array('-', '_'), base64_encode($encrypted_string));
139 | }
140 |
141 | /**
142 | * It generates a hash based in the hash type ($this->hashType), the data used and the SALT.
143 | *
144 | * @param mixed $data It could be any type of serializable data.
145 | * @return string If the serialization is not set, then it returns the same value.
146 | */
147 | public function hash($data): string
148 | {
149 | if (!is_string($data)) {
150 | $data = serialize($data);
151 | }
152 | if (!$this->encEnabled) {
153 | return $data;
154 | }
155 | return hash($this->hashType, $this->encSalt . $data);
156 | }
157 |
158 | //
159 |
160 | /**
161 | * It is a simple decryption. It's less safe but the result is shorter.
162 | * @param $data
163 | * @return string
164 | */
165 | public function decryptSimple($data): string
166 | {
167 | $result = '';
168 | $data = base64_decode(str_replace(array('-', '_'), array('+', '/'), $data));
169 | $l = strlen($data);
170 | for ($i = 0; $i < $l; $i++) {
171 | $char = $data[$i];
172 | $keychar = $this->encPassword[($i % strlen($this->encPassword)) - 1];
173 | $char = chr(ord($char) - ord($keychar));
174 | $result .= $char;
175 | }
176 | return $result;
177 | }
178 |
179 | /**
180 | * It is a simple encryption. It's less safe but generates a short string
181 | * @param $data
182 | * @return array|string|string[]
183 | */
184 | public function encryptSimple($data)
185 | {
186 | $result = '';
187 | $l = strlen($data);
188 | for ($i = 0; $i < $l; $i++) {
189 | $char = substr($data, $i, 1);
190 | $keychar = $this->encPassword[($i % strlen($this->encPassword)) - 1];
191 | $char = chr(ord($char) + ord($keychar));
192 | $result .= $char;
193 | }
194 | return str_replace(array('+', '/'), array('-', '_'), base64_encode($result));
195 | }
196 | //
197 |
198 |
199 | /**
200 | * @param string $password
201 | * @param string $salt
202 | * @param string $encMethod
203 | * @param bool $iv
204 | * @throws Exception
205 | */
206 | public function setEncryption(string $password,string $salt,string $encMethod,bool $iv = true): void
207 | {
208 | if (!extension_loaded('openssl')) {
209 | $this->encEnabled = false;
210 | throw new RuntimeException('OpenSSL not loaded, encryption disabled');
211 | }
212 | $this->encEnabled = true;
213 | $this->encPassword = $password;
214 | $this->encSalt = $salt;
215 | $this->encMethod = $encMethod;
216 | $this->iv = $iv;
217 | }
218 |
219 | /**
220 | * It changes the hash type.
221 | *
222 | * @param string $hashType =hash_algos()[$i]
223 | * @return void
224 | * @see https://www.php.net/manual/en/function.hash-algos.php
225 | */
226 | public function setHashType(string $hashType): void
227 | {
228 | $this->hashType = $hashType;
229 | }
230 | //
231 |
232 | /**
233 | * It encrypts an integer.
234 | * @param integer $n
235 | * @return int|false
236 | */
237 | public function encryptInteger(int $n)
238 | {
239 | if (!is_numeric($n)) {
240 | return false;
241 | }
242 | return (PHP_INT_SIZE === 4 ? $this->encrypt32($n) : $this->encrypt64($n)) ^ $this->encPassword;
243 | }
244 |
245 | /**
246 | * It decrypt an integer
247 | *
248 | * @param int $n
249 | * @return int|null
250 | */
251 | public function decryptInteger(int $n): ?int
252 | {
253 | if (!is_numeric($n)) {
254 | return null;
255 | }
256 | $n ^= $this->encPassword;
257 | return PHP_INT_SIZE === 4 ? $this->decrypt32($n) : $this->decrypt64($n);
258 | }
259 |
260 | /** @param int $n
261 | * @return int
262 | * @see PdoOneEncryption::encryptInteger
263 | */
264 | private function encrypt32(int $n): int
265 | {
266 | return ((0x000000FF & $n) << 24) + (((0xFFFFFF00 & $n) >> 8) & 0x00FFFFFF);
267 | }
268 |
269 | /** @param int $n
270 | * @return int
271 | * @see PdoOneEncryption::decryptInteger
272 | */
273 | private function decrypt32(int $n): int
274 | {
275 | return ((0x00FFFFFF & $n) << 8) + (((0xFF000000 & $n) >> 24) & 0x000000FF);
276 | }
277 |
278 | /** @param int $n
279 | * @return int
280 | * @see PdoOneEncryption::encryptInteger
281 | */
282 | private function encrypt64(int $n): int
283 | {
284 | /** @noinspection PhpCastIsUnnecessaryInspection */
285 | return ((0x000000000000FFFF & $n) << 48) + ((((int)0xFFFFFFFFFFFF0000 & $n) >> 16.0) & 0x0000FFFFFFFFFFFF);
286 | }
287 |
288 | /** @param int $n
289 | * @return int
290 | * @see PdoOneEncryption::decryptInteger
291 | */
292 | private function decrypt64(int $n): int
293 | {
294 | /** @noinspection PhpCastIsUnnecessaryInspection */
295 | return (((int)0x0000FFFFFFFFFFFF & $n) << 16.0) + ((((int)0xFFFF000000000000 & $n) >> 48.0) & 0x000000000000FFFF);
296 | }
297 | //
298 | }
299 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_TestMockup.php:
--------------------------------------------------------------------------------
1 | parent = $parent;
35 | }
36 |
37 | public function construct($charset, $config): string
38 | {
39 | $this->parent->database_delimiter0 = '';
40 | $this->parent->database_delimiter1 = '';
41 | $this->parent->database_identityName = 'identity';
42 | PdoOne::$isoDate = 'Ymd';
43 | PdoOne::$isoDateTime = 'Ymd H:i:s';
44 | PdoOne::$isoDateTimeMs = 'Ymd H:i:s.u';
45 | PdoOne::$isoDateInput = 'Ymd';
46 | PdoOne::$isoDateInputTime = 'Ymd H:i:s';
47 | PdoOne::$isoDateInputTimeMs = 'Ymd H:i:s.u';
48 | $this->parent->isOpen = false;
49 | return '';
50 | }
51 |
52 | public function connect($cs, $alterSession = false): void
53 | {
54 | $this->parent->conn1 = new stdClass();
55 | $this->parent->user = '';
56 | $this->parent->pwd = '';
57 | }
58 |
59 | public function truncate(string $tableName, string $extra, bool $force): bool
60 | {
61 | return true;
62 | }
63 |
64 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = ''): bool
65 | {
66 | return true;
67 | }
68 |
69 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
70 | {
71 | // $query="SELECT table_name as `table`,engine as `engine`, table_schema as `schema`,".
72 | // " table_collation as `collation`, table_comment as `description` ".
73 | $result = ['name' => 'name', 'engine' => 'engine', 'schema' => $this->parent->db
74 | , 'collation' => 'collation', 'description' => 'description'];
75 | if ($onlyDescription) {
76 | return $result['description'];
77 | }
78 | return $result;
79 | }
80 |
81 | public function getDefTable(string $table): array
82 | {
83 | $defArray = [
84 | [
85 | 'Field' => 'id',
86 | 'Key' => 'PRI',
87 | 'Type' => 'int',
88 | 'Null' => 'NO',
89 | 'Default' => '',
90 | 'Extra' => '',
91 | ],
92 | ];
93 | $result = [];
94 | foreach ($defArray as $col) {
95 | /*if ($col['Key'] === 'PRI') {
96 | $pk = $col['Field'];
97 | }*/
98 | $value = $col['Type'];
99 | $value .= ($col['Null'] === 'NO') ? ' not null' : '';
100 | if ($col['Default'] === 'CURRENT_TIMESTAMP') {
101 | $value .= ' default CURRENT_TIMESTAMP';
102 | } else {
103 | $value .= ($col['Default']) ? ' default \'' . $col['Default'] . '\'' : '';
104 | }
105 | $col['Extra'] = str_replace('DEFAULT_GENERATED ', '', $col['Extra']);
106 | $value .= ($col['Extra']) ? ' ' . $col['Extra'] : '';
107 | $result[$col['Field']] = $value;
108 | }
109 | return $result;
110 | }
111 |
112 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
113 | {
114 | if ($returnSimple) {
115 | $columns = ['col1' => 'PRIMARY KEY'];
116 | } else {
117 | $columns = ['col1' => ['key' => 'PRIMARY KEY', 'refcol' => 'col', 'reftable' => 'table2', 'extra' => '']];
118 | }
119 | return $this->parent->filterKey($filter, $columns, $returnSimple);
120 | }
121 |
122 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
123 | {
124 | if ($returnSimple) {
125 | $columns = ['col1' => 'FOREIGN KEY REFERENCES col [tableref](colref)'];
126 | } else {
127 | $columns = ['col1' => ['key' => 'FOREIGN KEY', 'refcol' => 'col', 'reftable' => 'table2', 'extra' => '']];
128 | }
129 | if ($assocArray) {
130 | return $columns;
131 | }
132 | return $this->parent->filterKey($filter, $columns, $returnSimple);
133 | }
134 |
135 | public function typeDict($row, bool $default = true): string
136 | {
137 | return '';
138 | }
139 |
140 | public function objectExist(string $type = 'table'): ?string
141 | {
142 | switch ($type) {
143 | case 'table':
144 | $query
145 | = "SELECT * FROM information_schema.tables where table_schema='{$this->parent->db}' and table_name=?";
146 | break;
147 | case 'function':
148 | $query
149 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where
150 | ROUTINE_SCHEMA='{$this->parent->db}'
151 | and ROUTINE_NAME=?
152 | and ROUTINE_TYPE='FUNCTION'";
153 | break;
154 | case 'procedure':
155 | $query
156 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where
157 | ROUTINE_SCHEMA='{$this->parent->db}'
158 | and ROUTINE_NAME=?
159 | and ROUTINE_TYPE='PROCEDURE'";
160 | break;
161 | default:
162 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
163 | '');
164 | die(1);
165 | }
166 | return $query;
167 | }
168 |
169 | public function objectList(string $type = 'table', bool $onlyName = false)
170 | {
171 | switch ($type) {
172 | case 'table':
173 | $query
174 | = "SELECT * FROM information_schema.tables where table_schema='{$this->parent->db}' and table_type='BASE TABLE'";
175 | if ($onlyName) {
176 | $query = str_replace('*', 'table_name', $query);
177 | }
178 | break;
179 | case 'function':
180 | $query
181 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where ROUTINE_SCHEMA='{$this->parent->db}'";
182 | if ($onlyName) {
183 | $query = str_replace('*', 'routine_name', $query);
184 | }
185 | break;
186 | default:
187 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
188 | '');
189 | die(1);
190 | }
191 | return $query;
192 | }
193 |
194 | public function columnTable($tableName): string
195 | {
196 | return "SELECT column_name colname
197 | ,data_type coltype
198 | ,character_maximum_length colsize
199 | ,numeric_precision colpres
200 | ,numeric_scale colscale
201 | ,if(column_key='PRI',1,0) iskey
202 | ,if(extra='auto_increment',1,0) isidentity
203 | ,if(is_nullable='NO',1,0) isnullable
204 | FROM information_schema.columns
205 | where table_schema='{$this->parent->db}' and table_name='$tableName'";
206 | }
207 |
208 | public function foreignKeyTable($tableName): string
209 | {
210 | return "SELECT col.name collocal
211 | ,objrem.name tablerem
212 | ,colrem.name colrem
213 | FROM columns fk
214 | where obj.name='$tableName' ";
215 | }
216 |
217 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
218 | {
219 | return ['CREATE TABLE'];
220 | }
221 |
222 | public function getSequence($sequenceName): string
223 | {
224 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
225 | return "select next_$sequenceName({$this->parent->nodeId}) id";
226 | }
227 |
228 | public function translateExtra($universalExtra): string
229 | {
230 | /** @noinspection DegradedSwitchInspection */
231 | switch ($universalExtra) {
232 | case 'autonumeric':
233 | $sqlExtra = 'GENERATED BY DEFAULT AS IDENTITY';
234 | break;
235 | default:
236 | $sqlExtra = $universalExtra;
237 | }
238 | return $sqlExtra;
239 | }
240 |
241 | public function translateType($universalType, $len = null): string
242 | {
243 | switch ($universalType) {
244 | case 'int':
245 | $sqlType = "int";
246 | break;
247 | case 'long':
248 | $sqlType = "long";
249 | break;
250 | case 'decimal':
251 | $sqlType = "decimal($len) ";
252 | break;
253 | case 'bool':
254 | $sqlType = "char(1)";
255 | break;
256 | case 'date':
257 | $sqlType = "date";
258 | break;
259 | case 'datetime':
260 | $sqlType = "datetime";
261 | break;
262 | case 'timestamp':
263 | $sqlType = "timestamp";
264 | break;
265 | case 'string':
266 | default:
267 | $sqlType = "varchar($len) ";
268 | break;
269 | }
270 | return $sqlType;
271 | }
272 |
273 |
274 | public function createTable(
275 | string $tableName,
276 | array $definition,
277 | $primaryKey = null,
278 | string $extra = '',
279 | string $extraOutside = ''
280 | ): string
281 | {
282 | $sql = "CREATE TABLE $tableName (";
283 | foreach ($definition as $key => $type) {
284 | $sql .= "$key $type,";
285 | }
286 | if ($primaryKey) {
287 | $sql .= " PRIMARY KEY(`$primaryKey`) ";
288 | } else {
289 | $sql = substr($sql, 0, -1);
290 | }
291 | $sql .= "$extra ) $extraOutside";
292 | return $sql;
293 | }
294 |
295 | public function addColumn(string $tableName,array $definition):string {
296 | $sql = "ALTER TABLE $tableName";
297 | foreach ($definition as $key => $type) {
298 | $sql .= "ADD COLUMN $key $type,";
299 | }
300 | return rtrim($sql,',');
301 | }
302 | public function deleteColumn(string $tableName, $columnName): string {
303 | $sql = "ALTER TABLE $tableName";
304 | if(!is_array($columnName)) {
305 | $columnName=[$columnName];
306 | }
307 | foreach($columnName as $c) {
308 | $sql .= "DROP COLUMN $c,";
309 | }
310 | return rtrim($sql,',');
311 | }
312 |
313 |
314 | public function createFK(string $tableName, array $foreignKeys): ?string
315 | {
316 | return "ALTER TABLE `$tableName` ADD CONSTRAINT `fk_{$tableName}_{key1}` FOREIGN KEY(`key1`);";
317 | }
318 |
319 | public function createIndex(string $tableName, array $indexesAndDef): string
320 | {
321 | $sql = '';
322 | foreach ($indexesAndDef as $key => $typeIndex) {
323 | $sql .= "ALTER TABLE `$tableName` ADD $typeIndex `idx_{$tableName}_$key` (`$key`) ;";
324 | }
325 | return $sql;
326 | }
327 |
328 | public function limit(?int $first, ?int $second): string
329 | {
330 | return $second === null ? ' limit ' . $first : " limit $first,$second";
331 | }
332 | public function now(): string
333 | {
334 | return 'select NOW() as NOW';
335 | }
336 |
337 | public function createTableKV($tableKV, $memoryKV = false): string
338 | {
339 | return $this->createTable($tableKV
340 | , ['KEYT' => 'VARCHAR(256)', 'VALUE' => 'MEDIUMTEXT', 'TIMESTAMP' => 'BIGINT']
341 | , 'KEYT', '', $memoryKV ? 'ENGINE = MEMORY' : '');
342 | }
343 |
344 | public function getPK($query, $pk = null): string
345 | {
346 | return 'primary_key';
347 | }
348 |
349 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = [])
350 | {
351 | // TODO: Implement callProcedure() method.
352 | }
353 |
354 | public function createProcedure(string $procedureName, $arguments = [], string $body = '', string $extra = '')
355 | {
356 | // TODO: Implement createProcedure() method.
357 | }
358 |
359 | public function db($dbname): string
360 | {
361 | return 'use ' . $dbname;
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_IExt.php:
--------------------------------------------------------------------------------
1 |
62 | * The results of the table depend on the kind of database. For example, sqlsrv returns the schema used (dbo),
63 | * while mysql returns the current schema (database).
64 | * Example:
65 | * ```
66 | * $this->getDefTableExtended('table'); // ['name','engine','schema','collation','description']
67 | * $this->getDefTableExtended('table',true); // "some description of the table"
68 | *
69 | * ```
70 | *
71 | * @param string $table The name of the table
72 | * @param bool $onlyDescription If true then it only returns a description
73 | *
74 | * @return array|string|null ['table','engine','schema','collation','description']
75 | * @throws Exception
76 | */
77 | public function getDefTableExtended(string $table, bool $onlyDescription = false);
78 |
79 | /**
80 | * Returns an associative array with the definition of a table (columns of the table).
81 | * Example:
82 | * ```
83 | * $this->getDefTable('table');
84 | * // ['col1'=>'int not null','col2'=>'varchar(50)']
85 | * ```
86 | * ```
87 | * array(4) {
88 | * ["actor_id"]=>
89 | * string(41) "smallint unsigned not null auto_increment"
90 | * ["first_name"]=>
91 | * string(20) "varchar(45) not null"
92 | * ["last_name"]=>
93 | * string(20) "varchar(45) not null"
94 | * ["last_update"]=>
95 | * string(72) "timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"
96 | * }
97 | * ```
98 | *
99 | * @param string $table
100 | *
101 | * @return array
102 | * @throws Exception
103 | */
104 | public function getDefTable(string $table): array;
105 |
106 | /**
107 | * Returns an associative array with the definition of keys of a table.
108 | * It includes primary key, key and unique keys
109 | *
110 | * @param string $table The name of the table to analize.
111 | * @param bool $returnSimple true= returns as a simple associative array
112 | * example:['id'=>'PRIMARY KEY','name'=>'FOREIGN KEY...']
113 | * false= returns as an associative array separated by parts
114 | * ['key','refcol','reftable','extra']
115 | * @param string|null $filter if not null then it only returns keys that match the condition
116 | *
117 | * @return array=["IndexName"=>'',"ColumnName"=>'',"is_unique"=>0,"is_primary_key"=>0,"TYPE"=>0]
118 | * @throws Exception
119 | */
120 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array;
121 |
122 | /**
123 | * Returns an associative array with the definition of foreign keys of a table.
124 | * It includes foreign keys.
125 | *
126 | * @param string $table The name of the table to analize.
127 | * @param bool $returnSimple true= returns as a simple associative array
128 | * example:['id'=>'PRIMARY KEY','name'=>'FOREIGN KEY...']
129 | * false= returns as an associative array separated by parts
130 | * ['key','refcol','reftable','extra']
131 | * @param string|null $filter if not null then it only returns keys that match the condition
132 | *
133 | * @param bool $assocArray If true then it returns an associative array (as value)
134 | * @return array
135 | * @throws Exception
136 | */
137 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array;
138 |
139 | public function db($dbname);
140 |
141 | /**
142 | * It returns a default value depending on the type of the column.
143 | *
144 | * @param $row
145 | * @param bool $default
146 | *
147 | * @return mixed
148 | */
149 | public function typeDict($row, bool $default = true);
150 |
151 | /**
152 | * Returns an associative array if the object exists. Otherwise, it will return an empty array
153 | * The fields of the associative array depends on the type of database
154 | *
155 | * @param string $type
156 | *
157 | * @return string|null (null on error)
158 | * @throws Exception
159 | */
160 | public function objectExist(string $type = 'table'): ?string;
161 |
162 | /**
163 | * Returns an associative array with the list of objects from the current schema.
164 | * The fields of the associative array depends on the type of database
165 | *
166 | * @param string $type =['table','function'][$i]
167 | * @param bool $onlyName If true then it only returns the name of the objects
168 | *
169 | * @return string|string[]|null null on error
170 | * @throws Exception
171 | */
172 | public function objectList(string $type = 'table', bool $onlyName = false);
173 |
174 | /**
175 | * ```
176 | * $result=[
177 | * [
178 | * 'colname'=>'idChamber', // the name of the column
179 | * 'coltype'=>'int', // the type is not converted.
180 | * 'colsize'=>NULL, // example 50 for varchar(50)
181 | * 'colpres'=>'10', // the precision, 10 digits (no decimals) is 10
182 | * 'colscale'=>0, // the scale, 2 decimals is 2
183 | * 'iskey'=>'1', // 1 if the column is key (example PK)
184 | * 'isidentity'=>'1', // 1 if the column is identity
185 | * 'isnullable'=>'1' // 1 if the columns is nullable or not
186 | * ]
187 | * ];
188 | * ```
189 | * @param string $tableName
190 | * @return string
191 | */
192 | public function columnTable(string $tableName): string;
193 |
194 | /**
195 | * It returns all the foreign keys of a table
196 | * **Example**
197 | * ```
198 | * $this->foreignKeyTable('table1);
199 | * //[['collocal'=>'col1',tablerem=>'table1',colrem=>'col1','fk_name'=>'table_fk1']]
200 | * ```
201 | * @param string $tableName
202 | * @return mixed
203 | */
204 | public function foreignKeyTable(string $tableName);
205 |
206 | /**
207 | * @param string|null $tableSequence
208 | * @param string $method
209 | *
210 | * @return array the sql command to create a sequence
211 | * @throws Exception
212 | */
213 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array;
214 |
215 | /**
216 | * It creates a store procedure
217 | * Example:
218 | * ```
219 | * // arg1 and arg2 are "in" arguments:
220 | * $this->createProcedure('proc1','in arg1 int,in arg2 varchar(50)','//body here');
221 | * // arg1 and arg2 are "in" arguments:
222 | * $this->createProcedure('proc1',['arg1'=>'int','arg2'=>'varchar(50)'],'//body here');
223 | * // arg1 is "in", arg2 is "out":
224 | * $this->createProcedure('proc1',
225 | * [
226 | * ['in','arg1','int'],
227 | * ['out','arg2','varchar(50)']
228 | * ],'//body here');
229 | * // arg1 is "in", arg2 is "in":
230 | * $this->createProcedure('proc1',
231 | * [
232 | * ['arg1','int'],
233 | * ['arg2','varchar(50)']
234 | * ],'//body here');
235 | * ```
236 | *
237 | * @param string $procedureName The name of the store procedure
238 | * @param array|string $arguments The arguments. It could be an associative array, a string or a multiple array
239 | * @param string $body The body of the store procedure
240 | * @param string $extra
241 | * @return false|int
242 | * @throws Exception
243 | */
244 | public function createProcedure(string $procedureName, $arguments = [], string $body = '', string $extra = '');
245 |
246 | public function getSequence(string $sequenceName);
247 |
248 | public function translateExtra(string $universalExtra): string;
249 |
250 | public function translateType(string $universalType, $len = null): string;
251 |
252 | /**
253 | * DCL command. It creates a database.
254 | * Example:
255 | * ```
256 | * $this->createtable("customer"
257 | * ,['id'=>'int','name'=>'varchar(50)']
258 | * ,'id');
259 | * $this->createtable("customer"
260 | * ,['id'=>'int','name'=>'varchar(50)']
261 | * ,['id'=>'PRIMARY KEY','name'=>'KEY']);
262 | * ```
263 | *
264 | *
265 | * @param string $tableName The name of the table
266 | * @param array $definition An associative array with the definition of the columns.
267 | * The key is used as the name of the field
268 | * @param null|string|array $primaryKey An associative array with the definition of the indexes/keys
269 | * The key is used as the name of the field.
270 | * 'field'=>'PRIMARY KEY'
271 | * 'field'=>'KEY'
272 | * 'field'=>'UNIQUE KEY'
273 | * 'field'=>'FOREIGN KEY REFERENCES TABLEREF(COLREF) ...'
274 | *
275 | * @param string $extra An extra definition inside the operation of create
276 | * @param string $extraOutside An extra definition after the operation of create
277 | *
278 | * @return string
279 | */
280 | public function createTable(string $tableName, array $definition, $primaryKey = null, string $extra = '',
281 | string $extraOutside = ''): string;
282 |
283 | /**
284 | * DCL command. It adds a column in a table.
285 | * Example:
286 | * ```
287 | * $this->addColumn("customer",['id'=>'int']);
288 | * $this->addColumn("customer",['id'=>'int not null']);
289 | * ```
290 | * @param string $tableName The name of the table
291 | * @param array $definition An associative array with the definition of the column.
292 | * The key is used as the name of the field
293 | * @return string
294 | */
295 | public function addColumn(string $tableName, array $definition): string;
296 |
297 | public function deleteColumn(string $tableName, $columnName): string;
298 |
299 | /**
300 | * Create foreign keys (other keys are ignored).
301 | *
302 | * @param string $tableName The name of the table
303 | * @param array $foreignKeys Associative array with the foreign key ['column'='FOREIGN KEY'].
304 | *
305 | * @return null|string the sql resultant
306 | * @throws Exception
307 | */
308 | public function createFK(string $tableName, array $foreignKeys): ?string;
309 |
310 | /**
311 | * Function to create a sql to create indexes.
312 | * @param string $tableName The name of the table
313 | * @param array $indexesAndDef Associative array with the indexes ['COLUMN'=>'TYPE INDEX'].
314 | * @return string the sql
315 | */
316 | public function createIndex(string $tableName, array $indexesAndDef): string;
317 |
318 | /**
319 | * It adds a limit operation for the query. It depends on the type of the database.
320 | * Example:
321 | * ```
322 | * ->select("")->limit("10,20")->toList();
323 | * ->select("")->limit(10,20)->toList();
324 | * ```
325 | *
326 | * @param int|null $first The whole expression separated by comma, or the first expression (the initial row)
327 | * @param int|null $second The numbers of row to read. If null, then it uses $sql.
328 | * @return string
329 | *
330 | * @throws Exception
331 | */
332 | public function limit(?int $first, ?int $second): string;
333 |
334 | public function now(): string;
335 |
336 | public function createTableKV($tableKV, $memoryKV = false): string;
337 |
338 | /**
339 | * It gets a primary key based in a query.
340 | * **Example**
341 | * ```
342 | * $this->getPk('table');
343 | * //[0=>['id']]
344 | * ```
345 | *
346 | * @param string $query query (only for MYSQL) or name of the table
347 | * @param string|array $pk Previous primary key (if the key is not found)
348 | *
349 | * @return array|mixed|string|false
350 | */
351 | public function getPK(string $query, $pk = null);
352 | }
353 |
--------------------------------------------------------------------------------
/lib/PdoOneCli.php:
--------------------------------------------------------------------------------
1 |
14 | * How to execute it?
15 | * In the command line, runs the next line:
16 | * ```
17 | * php vendor/eftec/PdoOne/lib/pdoonecli
18 | * or
19 | * vendor/bin/pdoonecli (Linux/macOS) / vendor/bin/pdoonecli.bat (Windows)
20 | * ```
21 | *
22 | * @see https://github.com/EFTEC/PdoOne
23 | * @package eftec
24 | * @author Jorge Castro Castillo
25 | * @copyright (c) Jorge Castro C. Dual Licence: MIT and Commercial License https://github.com/EFTEC/PdoOne
26 | * @version 2.6
27 | */
28 | class PdoOneCli
29 | {
30 | public const VERSION = '2.6';
31 | /** @var CliOne|null */
32 | public ?CliOne $cli = null;
33 | //protected $help;
34 | /** @var ?PdoOneCli the current instance */
35 | public static ?PdoOneCli $instance = null;
36 |
37 | public static function instance(bool $run = true): PdoOneCli
38 | {
39 | if (self::$instance === null) {
40 | self::$instance = new PdoOneCli($run);
41 | }
42 | return self::$instance;
43 | }
44 |
45 | public function __construct(bool $run = true)
46 | {
47 | self::$instance = $this;
48 | $this->cli = CliOne::instance();
49 | $this->cli->setErrorType();
50 | $this->cli->addMenu('mainmenu',
51 | function($cli) {
52 | $cli->upLevel('main menu');
53 | $cli->setColor(['byellow'])->showBread();
54 | }
55 | , 'footer');
56 | $this->cli->addMenuItem('mainmenu', 'connect',
57 | '[{{connect}}] Configure connection database', 'navigate:pdooneconnect');
58 | $this->cli->addMenu('pdooneconnect',
59 | function($cli) {
60 | $cli->upLevel('connect');
61 | $cli->setColor(['byellow'])->showBread();
62 | }
63 | , 'footer');
64 | $this->cli->addMenuItems('pdooneconnect', [
65 | 'configure' => ['[{{connect}}] configure and connect to the database', 'connectconfigure'],
66 | 'query' => ['[{{connect}}] run a query', 'connectquery'],
67 | 'load' => ['[{{connect}}] load the configuration', 'connectload'],
68 | 'save' => ['[{{connect}}] save the configuration', 'connectsave']
69 | ]);
70 | //$this->cli->addMenuItem('pdooneconnect');
71 | $this->cli->setVariable('connect', 'pending ');
72 | $listPHPFiles = $this->getFiles('.', '.config.php');
73 | $this->cli->createOrReplaceParam('fileconnect', [], 'longflag')
74 | ->setRequired(false)
75 | ->setCurrentAsDefault()
76 | ->setDescription('select a configuration file to load', 'Select the configuration file to use', [
77 | 'Example: "--fileconnect myconfig" ']
78 | , 'file')
79 | ->setDefault('')
80 | ->setInput(false, 'string', $listPHPFiles)
81 | ->evalParam();
82 | if ($this->cli->getParameter('fileconnect')->missing === false) {
83 | $this->doReadConfig();
84 | }
85 | if ($run) {
86 | if ($this->cli->getSTDIN() === null) {
87 | $this->showLogo();
88 | }
89 | try {
90 | $this->cli->evalMenu('mainmenu', $this);
91 | } catch (Exception $e) {
92 | }
93 | }
94 | }
95 |
96 | public function menuFooter(): void
97 | {
98 | $this->cli->downLevel();
99 | }
100 |
101 | public function menuConnectHeader(): void
102 | {
103 | $this->cli->upLevel('connect');
104 | $this->cli->setColor(['byellow'])->showBread();
105 | }
106 |
107 | /**
108 | * @return void
109 | */
110 | protected function pdoEvalParam(): void
111 | {
112 | $this->cli->evalParam('databaseType', true);
113 | $this->cli->evalParam('server', true);
114 | $this->cli->evalParam('user', true);
115 | $this->cli->evalParam('password', true);
116 | $this->cli->evalParam('database', true);
117 | $this->cli->evalParam('logFile', true);
118 | $this->cli->evalParam('logFile', true);
119 | $this->cli->evalParam('charset', true);
120 | $this->cli->evalParam('nodeId', true);
121 | $this->cli->evalParam('tableKV', true);
122 | $this->cli->evalParam('prefix', true);
123 | $this->cli->evalParam('postfix', true);
124 | }
125 |
126 | /** @noinspection PhpMissingReturnTypeInspection
127 | * @noinspection PhpUnused
128 | * @noinspection ReturnTypeCanBeDeclaredInspection
129 | */
130 | protected function runCliConnection($force = false)
131 | {
132 | if ($force === false && !$this->cli->getValue('databaseType')) {
133 | return null;
134 | }
135 | if ($force) {
136 | $this->pdoEvalParam();
137 | }
138 | $result = null;
139 | while (true) {
140 | try {
141 | $pdo = $this->createPdoInstance();
142 | if ($pdo === null) {
143 | throw new RuntimeException('trying');
144 | }
145 | $this->cli->showCheck('OK', 'green', 'Connected to the database ' . $this->cli->getValue('database') . ' ');
146 | $result = $pdo;
147 | break;
148 | } catch (Exception $ex) {
149 | }
150 | $rt = $this->cli->createParam('retry')
151 | ->setDescription('', 'Do you want to retry?')
152 | ->setInput(true, 'optionshort', ['yes', 'no'])->evalParam(true);
153 | if ($rt->value === 'no') {
154 | break;
155 | }
156 | $this->pdoEvalParam();
157 | } // retry database.
158 | return $result;
159 | }
160 |
161 | public function menuConnectSave(): void
162 | {
163 | $this->cli->upLevel('save');
164 | $this->cli->setColor(['byellow'])->showBread();
165 | $sg = $this->cli->createParam('yn', [], 'none')
166 | ->setDescription('', 'Do you want to save the configurations of connection?')
167 | ->setInput(true, 'optionshort', ['yes', 'no'])
168 | ->setDefault('yes')
169 | ->evalParam(true);
170 | if ($sg->value === 'yes') {
171 | $saveconfig = $this->cli->getParameter('fileconnect')->setInput()->evalParam(true);
172 | if ($saveconfig->value) {
173 | $arr = $this->pdoGetConfigArray();
174 | $r = $this->cli->saveDataPHPFormat($this->cli->getValue('fileconnect'), $arr);
175 | if ($r === '') {
176 | $this->cli->showCheck('OK', 'green', 'file saved correctly');
177 | }
178 | }
179 | }
180 | $this->cli->downLevel();
181 | }
182 |
183 | public function menuConnectQuery(): void
184 | {
185 | $this->cli->upLevel('query');
186 | $this->cli->setColor(['byellow'])->showBread();
187 | while (true) {
188 | $query = $this->cli->createOrReplaceParam('query', [], 'none')
189 | ->setAddHistory()
190 | ->setDescription('query', 'query (empty to exit)')
191 | ->setInput()
192 | ->setAllowEmpty()
193 | ->evalParam(true);
194 | if ($query->value === $this->cli->emptyValue || $query->value === '') {
195 | break;
196 | }
197 | $pdo = $this->createPdoInstance();
198 | if ($pdo !== null) {
199 | try {
200 | $result = $pdo->runRawQuery($query->value);
201 | $this->cli->showLine(json_encode($result, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
202 | } catch (Exception $e) {
203 | $this->cli->showCheck('ERROR', 'red', $e->getMessage());
204 | }
205 | } else {
206 | $this->cli->showCheck('ERROR', 'red', 'not connected');
207 | }
208 | }
209 | $this->cli->downLevel();
210 | }
211 |
212 | public function menuConnectload(): void
213 | {
214 | $this->cli->upLevel('load');
215 | $this->cli->setColor(['byellow'])->showBread();
216 | $saveconfig = $this->cli->getParameter('fileconnect')
217 | ->setInput()
218 | ->evalParam(true);
219 | if ($saveconfig->value) {
220 | $this->doReadConfig();
221 | }
222 | $this->cli->downLevel();
223 | }
224 |
225 | public function menuConnectConfigure(): void
226 | {
227 | while (true) {
228 | $this->cli->upLevel('configure');
229 | $this->cli->setColor(['byellow'])->showBread();
230 | $this->cli->createOrReplaceParam('databaseType', 'dt', 'longflag')
231 | ->setDescription('The type of database', 'Select the type of database', [
232 | 'Values allowed: '])
233 | ->setInput(true, 'optionshort', ['mysql', 'sqlsrv', 'oci', 'test'])
234 | ->setCurrentAsDefault()
235 | ->evalParam(true);
236 | $this->cli->createOrReplaceParam('server', 'srv', 'longflag')
237 | ->setDefault('127.0.0.1')
238 | ->setCurrentAsDefault()
239 | ->setDescription('The database server', 'Select the database server', [
240 | 'Example mysql: 127.0.0.1 , 127.0.0.1:3306 ',
241 | 'Example sqlsrv: (local)\sqlexpress 127.0.0.1\sqlexpress '])
242 | ->setInput()
243 | ->evalParam(true);
244 | $this->cli->createOrReplaceParam('user', 'u', 'longflag')
245 | ->setDescription('The username to access to the database', 'Select the username',
246 | ['Example: sa, root '], 'user')
247 | ->setRequired(false)
248 | ->setCurrentAsDefault()
249 | ->setInput()
250 | ->evalParam(true);
251 | $this->cli->createOrReplaceParam('pwd', 'p', 'longflag')
252 | ->setRequired(false)
253 | ->setDescription('The password to access to the database', '', ['Example: 12345 '], 'pwd')
254 | ->setCurrentAsDefault()
255 | ->setInput(true, 'password')
256 | ->evalParam(true);
257 | $this->cli->createOrReplaceParam('database', 'db', 'longflag')
258 | ->setRequired(false)
259 | ->setDescription('The database/schema', 'Select the database/schema', [
260 | 'Example: sakila,contoso,adventureworks '], 'db')
261 | ->setCurrentAsDefault()
262 | ->setInput()
263 | ->evalParam(true);
264 | $this->cli->createOrReplaceParam('logFile', [], 'longflag')
265 | ->setRequired(false)
266 | ->setDefault('no')
267 | ->setDescription('Do you want to log into a file?', '', ['Example: yes '])
268 | ->setCurrentAsDefault()
269 | ->setInput(true, 'optionshort', ['yes', 'no'])
270 | ->evalParam(true);
271 | $this->cli->createOrReplaceParam('charset', [], 'longflag')
272 | ->setRequired(false)
273 | ->setDefault(null)
274 | ->setAllowEmpty()
275 | ->setDescription('Select a charset (or empty for default)', '', ['Example: utf8mb4 '])
276 | ->setCurrentAsDefault()
277 | ->setInput()
278 | ->evalParam(true);
279 | $this->cli->createOrReplaceParam('nodeId', [], 'longflag')
280 | ->setRequired(false)
281 | ->setDefault(1)
282 | ->setDescription('Select the node id used by snowflake, (or empty for default)', '', ['Example: 1 '])
283 | ->setCurrentAsDefault()
284 | ->setInput(true, 'number')
285 | ->evalParam(true);
286 | $this->cli->createOrReplaceParam('tableKV', [], 'longflag')
287 | ->setRequired(false)
288 | ->setDefault('')
289 | ->setAllowEmpty()
290 | ->setDescription('select the table key-value (or empty for default)', '', ['Example: table1 '])
291 | ->setCurrentAsDefault()
292 | ->setInput()
293 | ->evalParam(true);
294 | $this->cli->createOrReplaceParam('prefix', [], 'longflag')
295 | ->setRequired(false)
296 | ->setDefault('_')
297 | ->setAllowEmpty()
298 | ->setDescription('The prefix', '', ['Example: _ '])
299 | ->setCurrentAsDefault()
300 | ->setInput()
301 | ->evalParam(true);
302 | $this->cli->createOrReplaceParam('postfix', [], 'longflag')
303 | ->setRequired(false)
304 | ->setDefault("")
305 | ->setAllowEmpty()
306 | ->setDescription('Select the postfix', '', ['Example: Def '])
307 | ->setCurrentAsDefault()
308 | ->setInput()
309 | ->evalParam(true);
310 | $this->cli->downLevel();
311 | try {
312 | $pdo = $this->createPdoInstance();
313 | if ($pdo === null) {
314 | throw new RuntimeException('trying');
315 | }
316 | $this->cli->showCheck('OK', 'green', 'Connected to the database ' . $this->cli->getValue('database') . ' ');
317 | $this->cli->setVariable('connect', 'ok ');
318 | //$result = $pdo;
319 | break;
320 | } catch (Exception $ex) {
321 | }
322 | $rt = $this->cli->createParam('retry')
323 | ->setDescription('', 'Do you want to retry?')
324 | ->setInput(true, 'optionshort', ['yes', 'no'])->evalParam(true);
325 | if ($rt->value === 'no') {
326 | break;
327 | }
328 | }
329 | }
330 |
331 | public function pdoGetConfigArray(): array
332 | {
333 | $r = $this->cli->getValueAsArray(
334 | ['databaseType', 'server', 'user', 'pwd', 'database', 'logFile', 'charset', 'nodeId', 'tableKV', 'prefix', 'postfix']);
335 | $r['logFile'] = $r['logFile'] === 'yes';
336 | return $r;
337 | }
338 |
339 | public function pdoSetConfigArray(array $array): void
340 | {
341 | $lf = $array['logFile'] ?? 'false';
342 | $backup = $this->cli->getValue('logFile'); //yes|no
343 | $this->cli->setParam('logFile', $lf ? 'yes' : 'no', false, true); //true|false
344 | $this->cli->setParamUsingArray($array, ['databaseType', 'server', 'user', 'pwd', 'database', 'logFile', 'charset', 'nodeId', 'tableKV', 'prefix', 'postfix']);
345 | $this->cli->setParam('logFile', $backup);//yes|no
346 | }
347 |
348 | public function doReadConfig(): void
349 | {
350 | $r = $this->cli->readDataPHPFormat($this->cli->getValue('fileconnect'));
351 | if ($r !== null && $r[0] === true) {
352 | $this->cli->showCheck('OK', 'green', 'file read correctly');
353 | $this->cli->setVariable('connect', 'ok ');
354 | $this->pdoSetConfigArray($r[1]);
355 | PdoOne::$prefixBase = $this->cli->getValue('prefix') ?? '_';
356 | PdoOne::$postfixBase = $this->cli->getValue('postfix') ?? '';
357 | } else {
358 | $this->cli->showCheck('ERROR', 'red', 'unable to read file ' . $this->cli->getValue('fileconnect') . ", cause " . $r[1]);
359 | }
360 | }
361 |
362 | /** @noinspection PhpMissingReturnTypeInspection
363 | * @noinspection ReturnTypeCanBeDeclaredInspection
364 | */
365 | public function createPdoInstance()
366 | {
367 | $pdo = null;
368 | try {
369 | if ($this->cli->getValue('databaseType') === null
370 | || $this->cli->getValue('server') === null
371 | || $this->cli->getValue('user') === null
372 | || $this->cli->getValue('pwd') === null
373 | || $this->cli->getValue('database') === null
374 | ) {
375 | throw new RuntimeException('No configuration');
376 | }
377 | $r = $this->pdoGetConfigArray();
378 | $r = array_values($r);
379 | $pdo = new PdoOne(...$r);
380 | $pdo->logLevel = 1;
381 | $pdo->connect();
382 | } catch (Exception $ex) {
383 | if ($pdo !== null) {
384 | $this->cli->showCheck('ERROR', 'red', ['Unable to connect to database', $pdo->lastError(), $pdo->errorText]);
385 | } else {
386 | $this->cli->showCheck('ERROR', 'red', ['Unable to connect to database', $ex->getMessage()]);
387 | }
388 | return null;
389 | }
390 | $pdo->logLevel = 2;
391 | return $pdo;
392 | }
393 |
394 | public function getCli(): CliOne
395 | {
396 | return $this->cli;
397 | }
398 |
399 | public static function isCli(): bool
400 | {
401 | return !http_response_code();
402 | }
403 |
404 | /***
405 | * It finds the vendor path (where composer is located).
406 | * @param string|null $initPath
407 | * @return string
408 | *
409 | */
410 | public static function findVendorPath(?string $initPath = null): string
411 | {
412 | $initPath = $initPath ?: __DIR__;
413 | $prefix = '';
414 | $defaultvendor = $initPath;
415 | // finding vendor
416 | for ($i = 0; $i < 8; $i++) {
417 | if (@file_exists("$initPath/{$prefix}vendor/autoload.php")) {
418 | $defaultvendor = "{$prefix}vendor";
419 | break;
420 | }
421 | $prefix .= '../';
422 | }
423 | return $defaultvendor;
424 | }
425 |
426 | /**
427 | * It gets a list of files filtered by extension.
428 | * @param string $path
429 | * @param string $extension . Example: ".php", "php" (it could generate false positives)
430 | * @return array
431 | */
432 | protected function getFiles(string $path, string $extension): array
433 | {
434 | $scanned_directory = array_diff(scandir($path), ['..', '.']);
435 | $scanned2 = [];
436 | foreach ($scanned_directory as $k) {
437 | $fullname = pathinfo($k)['extension'] ?? '';
438 | if ($this->str_ends_with($fullname, $extension)) {
439 | $scanned2[$k] = $k;
440 | }
441 | }
442 | return $scanned2;
443 | }
444 |
445 | /**
446 | * for PHP <8.0 compatibility
447 | * @param string $haystack
448 | * @param string $needle
449 | * @return bool
450 | *
451 | */
452 | protected function str_ends_with(string $haystack, string $needle): bool
453 | {
454 | $needle_len = strlen($needle);
455 | $haystack_len = strlen($haystack);
456 | if ($haystack_len < $needle_len) {
457 | return false;
458 | }
459 | return ($needle_len === 0 || 0 === substr_compare($haystack, $needle, -$needle_len));
460 | }
461 |
462 | protected function showLogo(): void
463 | {
464 | $v = PdoOne::VERSION;
465 | $vc = self::VERSION;
466 | $this->cli->show("
467 | _____ _ _____
468 | | _ | _| | ___ | | ___ ___
469 | | __|| . || . || | || || -_|
470 | |__| |___||___||_____||_|_||___|
471 | PdoOne: $v Cli: $vc
472 |
473 | Syntax:php " . basename(__FILE__) . "
474 |
475 | ");
476 | try {
477 | $this->cli->showParamSyntax2();
478 | } catch (Exception $ex) {
479 | }
480 | }
481 |
482 | /**
483 | * It is used internally to merge two arrays.
484 | * @noinspection PhpUnused
485 | */
486 | protected function updateMultiArray(?array $oldArray, ?array $newArray, string $name): ?array
487 | {
488 | if (count($newArray) !== 0) {
489 | // delete
490 | foreach ($newArray as $tableName => $columns) {
491 | if (isset($oldArray[$tableName])) {
492 | foreach ($columns as $column => $v) {
493 | if (!array_key_exists($column, $oldArray[$tableName])) {
494 | $this->cli->showCheck('deleted ', 'red', "$name: Column $tableName.$column deleted");
495 | unset($newArray[$tableName][$column]);
496 | }
497 | }
498 | } else {
499 | $this->cli->showCheck('deleted ', 'red', "$name: Table $tableName delete");
500 | unset($newArray[$tableName]);
501 | }
502 | }
503 | // insert
504 | foreach ($oldArray as $tableName => $columns) {
505 | if (isset($newArray[$tableName])) {
506 | foreach ($columns as $column => $v) {
507 | if (!array_key_exists($column, $newArray[$tableName])) {
508 | $this->cli->showCheck(' added ', 'green', "$name: Column $tableName.$column added");
509 | $newArray[$tableName][$column] = $v;
510 | //unset($this->tablexclass[$tableName], $this->columnsTable[$tableName], $this->extracolumn[$tableName]);
511 | }
512 | }
513 | } else {
514 | $this->cli->showCheck(' added ', 'green', "$name: Table $tableName added");
515 | $newArray[$tableName] = $columns;
516 | }
517 | }
518 | } else {
519 | $newArray = $oldArray;
520 | }
521 | return $newArray;
522 | }
523 | }
524 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_Sqlite.php:
--------------------------------------------------------------------------------
1 | parent = $parent;
37 | }
38 |
39 | public function construct($charset, $config): string
40 | {
41 | $this->parent->database_delimiter0 = '[';
42 | $this->parent->database_delimiter1 = ']';
43 | $this->parent->database_identityName = 'auto_increment';
44 | $charset = ($charset == null) ? 'utf8' : $charset;
45 | PdoOne::$dateFormat = 'Y-m-d';
46 | PdoOne::$dateTimeFormat = 'Y-m-d H:i:s';
47 | PdoOne::$dateTimeMicroFormat = 'Y-m-d H:i:s.u';
48 | PdoOne::$isoDateInput = 'Y-m-d';
49 | PdoOne::$isoDateInputTime = 'Y-m-d H:i:s';
50 | PdoOne::$isoDateInputTimeMs = 'Y-m-d H:i:s.u';
51 | $this->parent->isOpen = false;
52 | return $charset;
53 | }
54 |
55 | public function connect($cs, $alterSession = false): void
56 | {
57 | $this->parent->conn1
58 | = new PDO("sqlite:{$this->parent->server}");
59 | $this->parent->user = '';
60 | $this->parent->pwd = '';
61 | $this->parent->conn1->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
62 | }
63 |
64 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = []): bool
65 | {
66 | $keys = array_keys($arguments);
67 | $outputFields = '';
68 | $argList = '';
69 | if (count($keys) > 0) {
70 | foreach ($arguments as $k => $v) {
71 | /** @noinspection TypeUnsafeArraySearchInspection */
72 | if (in_array($k, $outputColumns)) {
73 | $argList .= "@$k,";
74 | $outputFields .= "@$k as [$k],";
75 | $stmt = $this->parent->prepare("set @$k=:$k");
76 | $stmt->bindParam($k, $v, $this->parent->getType($v));
77 | $stmt->execute();
78 | } else {
79 | $argList .= ":$k,";
80 | }
81 | }
82 | $argList = trim($argList, ','); // remove the trail comma
83 | $outputFields = trim($outputFields, ',');
84 | }
85 | $stmt = $this->parent->prepare("call $procName($argList)");
86 | foreach ($arguments as $k => $v) {
87 | /** @noinspection TypeUnsafeArraySearchInspection */
88 | if (!in_array($k, $outputColumns)) {
89 | $stmt->bindParam($k, $arguments[$k], $this->parent->getType($arguments[$k]));
90 | }
91 | }
92 | $stmt->execute();
93 | if ($outputFields !== '') {
94 | $stmt = $this->parent->prepare("select $outputFields");
95 | $stmt->execute();
96 | $var = $stmt->fetch(PDO::FETCH_ASSOC);
97 | $arguments = array_merge($arguments, $var);
98 | }
99 | $stmt = null;
100 | return true;
101 | }
102 |
103 | public function truncate(string $tableName, string $extra, bool $force)
104 | {
105 | if (!$force) {
106 | $sql = 'truncate table ' . $this->parent->addDelimiter($tableName) . " $extra";
107 | return $this->parent->runRawQuery($sql);
108 | }
109 | $sql = "SET FOREIGN_KEY_CHECKS = 0;
110 | TRUNCATE " . $this->parent->addDelimiter($tableName) . " $extra;
111 | SET FOREIGN_KEY_CHECKS = 1;";
112 | return $this->parent->runMultipleRawQuery($sql, true);
113 | }
114 |
115 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = '')
116 | {
117 | $sql = "ALTER TABLE " . $this->parent->addDelimiter($tableName) . " AUTO_INCREMENT = $newValue";
118 | return $this->parent->runRawQuery($sql);
119 | }
120 |
121 | /**
122 | * @param string $table
123 | * @param bool $onlyDescription
124 | *
125 | * @return array|string|null ['table','engine','schema','collation','description']
126 | * @throws Exception
127 | */
128 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
129 | {
130 | $query = "name as [table],'' as table_schema,'' as collation,'' as description " .
131 | "FROM sqlite_master WHERE type='table' and table_name='?'";
132 | $result = $this->parent->runRawQuery($query, [ $table]);
133 | if ($onlyDescription) {
134 | return $result['description'];
135 | }
136 | return $result;
137 | }
138 |
139 | /**
140 | * @param string $table
141 | * @return array ['Field','Type','Null','Key','Default','Extra']
142 | * @throws Exception
143 | */
144 | public function getDefTable(string $table): array
145 | {
146 | $defArray = $this->parent->runRawQuery('show columns from ' . $table, []);
147 | $result = [];
148 | foreach ($defArray as $col) {
149 | /*if ($col['Key'] === 'PRI') {
150 | $pk = $col['Field'];
151 | }*/
152 | $type = $col['Type'];
153 | $type = str_replace('int(11)', 'int', $type);
154 | $value = $type;
155 | $value .= ($col['Null'] === 'NO') ? ' not null' : '';
156 | if ($col['Default'] === 'CURRENT_TIMESTAMP') {
157 | $value .= ' default CURRENT_TIMESTAMP';
158 | } else {
159 | $value .= ($col['Default']) ? ' default ' . PdoOne::addParenthesis($col['Default'], "'", "'") : '';
160 | }
161 | $col['Extra'] = str_replace('DEFAULT_GENERATED ', '', $col['Extra']);
162 | $value .= ($col['Extra']) ? ' ' . $col['Extra'] : '';
163 | $result[$col['Field']] = $value;
164 | }
165 | return $result;
166 | }
167 |
168 |
169 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
170 | {
171 | throw new RuntimeException('pendiente');
172 | $columns = [];
173 | /** @var array $result =array(["CONSTRAINT_NAME"=>'',"COLUMN_NAME"=>'',"REFERENCED_TABLE_NAME"=>''
174 | * ,"REFERENCED_COLUMN_NAME"=>'',"UPDATE_RULE"=>'',"DELETE_RULE"=>''])
175 | */
176 | $fkArr = $this->parent->select('k.CONSTRAINT_NAME,k.COLUMN_NAME,k.REFERENCED_TABLE_NAME
177 | ,k.REFERENCED_COLUMN_NAME,c.UPDATE_RULE,c.DELETE_RULE,k.POSITION_IN_UNIQUE_CONSTRAINT')
178 | ->from('INFORMATION_SCHEMA.KEY_COLUMN_USAGE k')
179 | ->innerjoin('information_schema.REFERENTIAL_CONSTRAINTS c
180 | ON k.referenced_table_schema=c.CONSTRAINT_SCHEMA AND k.CONSTRAINT_NAME=c.CONSTRAINT_NAME')
181 | ->where('k.TABLE_SCHEMA=? AND k.TABLE_NAME = ?', [$this->parent->db, $table])
182 | ->order('k.POSITION_IN_UNIQUE_CONSTRAINT,k.COLUMN_NAME')->toList(); // to sort by the position is important.
183 | foreach ($fkArr as $k => $item) {
184 | if ($item['POSITION_IN_UNIQUE_CONSTRAINT'] == 2) {
185 | // constraint uses two columns
186 | $find = null;
187 | foreach ($fkArr as $k2 => $item2) {
188 | if ($item['CONSTRAINT_NAME'] === $item2['CONSTRAINT_NAME']) {
189 | $find = $k2;
190 | break;
191 | }
192 | }
193 | if ($find !== null) {
194 | $fkArr[$find]['REFERENCED_COLUMN_NAME'] .= ',' . $item['REFERENCED_COLUMN_NAME'];
195 | $fkArr[$k] = null;
196 | //unset($fkArr[$k]);
197 | }
198 | }
199 | }
200 | foreach ($fkArr as $item) {
201 | if ($item !== null) {
202 | $rcn = $item['REFERENCED_COLUMN_NAME'];
203 | if (strpos($rcn, ',') !== false) {
204 | $tmp = explode(',', $rcn);
205 | $rcn = "[$tmp[0]],[$tmp[1]]";
206 | } else {
207 | $rcn = "[$rcn]";
208 | }
209 | $txt = "FOREIGN KEY REFERENCES[{$item['REFERENCED_TABLE_NAME']}]($rcn)";
210 | $extra = '';
211 | if ($item['UPDATE_RULE'] && $item['UPDATE_RULE'] !== 'NO ACTION') {
212 | $extra .= ' ON UPDATE ' . $item['UPDATE_RULE'];
213 | }
214 | if ($item['DELETE_RULE'] && $item['DELETE_RULE'] !== 'NO ACTION') {
215 | $extra .= ' ON DELETE ' . $item['DELETE_RULE'];
216 | }
217 | if ($returnSimple) {
218 | $columns[$item['COLUMN_NAME']] = $txt . $extra;
219 | } else {
220 | $columns[$item['COLUMN_NAME']] = PdoOne::newColFK('FOREIGN KEY', $item['REFERENCED_COLUMN_NAME'],
221 | $item['REFERENCED_TABLE_NAME'], $extra, $item['CONSTRAINT_NAME']);
222 | $columns[PdoOne::addPrefix($item['COLUMN_NAME'])] = PdoOne::newColFK('MANYTOONE', $item['REFERENCED_COLUMN_NAME'],
223 | $item['REFERENCED_TABLE_NAME'], $extra, $item['CONSTRAINT_NAME']);
224 | }
225 | }
226 | }
227 | if ($assocArray) {
228 | return $columns;
229 | }
230 | return $this->parent->filterKey($filter, $columns, $returnSimple);
231 | }
232 |
233 | public function typeDict($row, bool $default = true): string
234 | {
235 | $type = @$row['native_type'];
236 | switch ($type) {
237 | case 'VAR_STRING':
238 | case 'BLOB':
239 | case 'TINY_BLOB':
240 | case 'MEDIUM_BLOB':
241 | case 'LONG_BLOB':
242 | case 'STRING':
243 | case 'GEOMETRY':
244 | case 'TIMESTAMP':
245 | case 'TIME':
246 | case 'DATE':
247 | case 'DATETIME':
248 | case 'NULL':
249 | return ($default) ? "''" : 'string';
250 | case 'LONG':
251 | case 'LONGLONG':
252 | case 'SHORT':
253 | case 'INT24':
254 | case 'TINY':
255 | case 'YEAR':
256 | return ($default) ? '0' : 'int';
257 | case 'DECIMAL':
258 | case 'DOUBLE':
259 | case 'FLOAT':
260 | case 'NEWDECIMAL':
261 | return ($default) ? '0.0' : 'float';
262 | default:
263 | return '???' . $type;
264 | }
265 | }
266 |
267 | public function objectExist(string $type = 'table'): ?string
268 | {
269 | // SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';
270 | switch ($type) {
271 | case 'table':
272 | $query
273 | = "SELECT * FROM sqlite_master where type='$type' and name=?";
274 | break;
275 | default:
276 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
277 | '');
278 | return null;
279 | }
280 | return $query;
281 | }
282 |
283 | public function objectList(string $type = 'table', bool $onlyName = false)
284 | {
285 | switch ($type) {
286 | case 'table':
287 | $query
288 | = "SELECT * FROM sqlite_master where type='table'";
289 | if ($onlyName) {
290 | $query = str_replace('*', 'name', $query);
291 | }
292 | break;
293 | case 'index':
294 | $query
295 | = "SELECT * FROM sqlite_master where type='index'";
296 | if ($onlyName) {
297 | $query = str_replace('*', 'name', $query);
298 | }
299 | break;
300 | default:
301 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
302 | '');
303 | return null;
304 | }
305 | return $query;
306 | }
307 |
308 | public function columnTable($tableName): string
309 | {
310 | return "PRAGMA table_info([$tableName])";
311 | }
312 |
313 | public function foreignKeyTable($tableName): string
314 | {
315 | return "PRAGMA foreign_key_list([$tableName])";
316 |
317 | }
318 |
319 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
320 | {
321 | $ok = $this->parent->createTable($tableSequence, [
322 | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
323 | 'stub' => "char(1) NOT NULL DEFAULT ''",
324 | ], [
325 | 'id' => 'PRIMARY KEY',
326 | 'stub' => 'UNIQUE KEY'
327 | ], '', 'ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8');
328 | if (!$ok) {
329 | $this->parent->throwError("Unable to create table $tableSequence", '');
330 | return [''];
331 | }
332 | $ok = $this->parent->insert($tableSequence, ['stub' => 'a']);
333 | if (!$ok) {
334 | $this->parent->throwError("Unable to insert in table $tableSequence", '');
335 | return [''];
336 | }
337 | $sql = 'SET GLOBAL log_bin_trust_function_creators = ?';
338 | try {
339 | $this->parent->runRawQuery($sql, [1]);
340 | } catch(Exception $ex) {
341 | // it crashed with "Wrong COM_STMT_PREPARE response size. Received 7"
342 | // however, the operation is correctly executed, so let's do nothing.
343 | }
344 |
345 | if ($method === 'snowflake') {
346 | $sql = "CREATE FUNCTION `next_$tableSequence`(node integer) RETURNS BIGINT(20)
347 | MODIFIES SQL DATA
348 | NOT DETERMINISTIC
349 | BEGIN
350 | DECLARE epoch BIGINT(20);
351 | DECLARE current_ms BIGINT(20);
352 | DECLARE incr BIGINT(20);
353 | SET current_ms = round(UNIX_TIMESTAMP(CURTIME(4)) * 1000);
354 | SET epoch = 1459440000000;
355 | REPLACE INTO $tableSequence (stub) VALUES ('a');
356 | SELECT LAST_INSERT_ID() INTO incr;
357 | RETURN (current_ms - epoch) << 22 | (node << 12) | (incr % 4096);
358 | END";
359 | } else {
360 | $sql = "CREATE DEFINER=CURRENT_USER FUNCTION `next_$tableSequence`(node integer) RETURNS BIGINT(20)
361 | MODIFIES SQL DATA
362 | NOT DETERMINISTIC
363 | BEGIN
364 | DECLARE incr BIGINT(20);
365 | REPLACE INTO $tableSequence (stub) VALUES ('a');
366 | SELECT LAST_INSERT_ID() INTO incr;
367 | RETURN incr;
368 | END";
369 | }
370 | return [$sql];
371 | }
372 |
373 | /**
374 | * @param string $procedureName
375 | * @param string|array $arguments
376 | * @param string $body
377 | * @param string $extra
378 | * @return string
379 | */
380 | public function createProcedure(string $procedureName, $arguments = '', string $body = '', string $extra = ''): string
381 | {
382 | throw new RuntimeException('Sqlite does not support store procedure');
383 | }
384 |
385 | public function getSequence($sequenceName): string
386 | {
387 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
388 | return "select next_$sequenceName({$this->parent->nodeId}) id";
389 | }
390 |
391 | public function translateExtra($universalExtra): string
392 | {
393 | /** @noinspection DegradedSwitchInspection */
394 | switch ($universalExtra) {
395 | case 'autonumeric':
396 | $sqlExtra = 'AUTO_INCREMENT';
397 | break;
398 | default:
399 | $sqlExtra = $universalExtra;
400 | }
401 | return $sqlExtra;
402 | }
403 |
404 | public function translateType($universalType, $len = null): string
405 | {
406 | switch ($universalType) {
407 | case 'int':
408 | $sqlType = "int";
409 | break;
410 | case 'long':
411 | $sqlType = "long";
412 | break;
413 | case 'decimal':
414 | $sqlType = "decimal($len) ";
415 | break;
416 | case 'bool':
417 | $sqlType = "char(1)";
418 | break;
419 | case 'date':
420 | $sqlType = "date";
421 | break;
422 | case 'datetime':
423 | $sqlType = "datetime";
424 | break;
425 | case 'timestamp':
426 | $sqlType = "timestamp";
427 | break;
428 | case 'string':
429 | default:
430 | $sqlType = "varchar($len) ";
431 | break;
432 | }
433 | return $sqlType;
434 | }
435 |
436 |
437 | public function createTable(
438 | string $tableName,
439 | array $definition,
440 | $primaryKey = null,
441 | string $extra = '',
442 | string $extraOutside = ''
443 | ): string
444 | {
445 | $extraOutside = ($extraOutside === '') ? ""
446 | : $extraOutside;
447 | $sql = "CREATE TABLE [$tableName] (";
448 | foreach ($definition as $key => $type) {
449 | $sql .= "[$key] $type,";
450 | }
451 | if ($primaryKey) {
452 | if (is_array($primaryKey)) {
453 | $hasPK = false;
454 | foreach ($primaryKey as $key => $value) {
455 | $valueKey = (is_array($value)) ? reset($value) : $value;
456 | $p0 = stripos($valueKey . ' ', 'KEY ');
457 | if ($p0 === false) {
458 | trigger_error('createTable: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
459 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
460 | break;
461 | }
462 | $type = strtoupper(trim(substr($valueKey, 0, $p0)));
463 | $value = substr($valueKey, $p0 + 4);
464 | switch ($type) {
465 | case 'PRIMARY':
466 | if (!$hasPK) {
467 | $sql .= "PRIMARY KEY ([$key]*pk*) $value,";
468 | $hasPK = true;
469 | } else {
470 | $sql = str_replace('*pk*', ",[$key]", $sql);
471 | }
472 | break;
473 | case '':
474 | $sql .= "KEY `{$tableName}_{$key}_idx` ([$key]) $value,";
475 | break;
476 | case 'UNIQUE':
477 | $sql .= "UNIQUE KEY `{$tableName}_{$key}_idx` ([$key]) $value,";
478 | break;
479 | default:
480 | trigger_error("createTable: [$type KEY] not defined");
481 | break;
482 | }
483 | }
484 | $sql = str_replace('*pk*', '', $sql);
485 | $sql = rtrim($sql, ',');
486 | } else {
487 | $sql .= " PRIMARY KEY([$primaryKey]) ";
488 | }
489 | } else {
490 | $sql = substr($sql, 0, -1);
491 | }
492 | $sql .= "$extra ) " . $extraOutside;
493 | return $sql;
494 | }
495 |
496 | public function addColumn(string $tableName,array $definition):string {
497 | $sql = "ALTER TABLE [$tableName]";
498 | foreach ($definition as $key => $type) {
499 | $sql .= "ADD COLUMN [$key] $type,";
500 | }
501 | return rtrim($sql,',');
502 | }
503 | public function deleteColumn(string $tableName, $columnName): string {
504 | $sql = "ALTER TABLE [$tableName]";
505 | if(!is_array($columnName)) {
506 | $columnName=[$columnName];
507 | }
508 | foreach($columnName as $c) {
509 | $sql .= "DROP COLUMN $c,";
510 | }
511 | return rtrim($sql,',');
512 | }
513 |
514 | /** @noinspection SqlResolve */
515 | public function createFK(string $tableName, array $foreignKeys): string
516 | {
517 | $sql = '';
518 | foreach ($foreignKeys as $key => $value) {
519 | $p0 = stripos($value . ' ', 'KEY ');
520 | if ($p0 === false) {
521 | trigger_error('createTable: Key with a wrong syntax: "FOREIGN KEY.." ');
522 | break;
523 | }
524 | $type = strtoupper(trim(substr($value, 0, $p0)));
525 | $value = substr($value, $p0 + 4);
526 | if ($type === 'FOREIGN') {
527 | $sql .= "ALTER TABLE [$tableName] ADD CONSTRAINT `fk_{$tableName}_$key` FOREIGN KEY([$key]) $value;";
528 | }
529 | }
530 | return $sql;
531 | }
532 |
533 | public function createIndex(string $tableName, array $indexesAndDef): string
534 | {
535 | $sql = '';
536 | foreach ($indexesAndDef as $key => $typeIndex) {
537 | $sql .= "ALTER TABLE [$tableName] ADD $typeIndex [idx_{$tableName}_$key] ([$key]) ;";
538 | }
539 | return $sql;
540 | }
541 |
542 | public function limit(?int $first, ?int $second): string
543 | {
544 | return $second === null ? ' limit ' . $first : " limit $first,$second";
545 | }
546 | public function now(): string
547 | {
548 | return 'select NOW() as NOW';
549 | }
550 |
551 | public function createTableKV($tableKV, $memoryKV = false): string
552 | {
553 | return $this->createTable($tableKV
554 | , ['KEYT' => 'VARCHAR(256)', 'VALUE' => 'MEDIUMTEXT', 'TIMESTAMP' => 'BIGINT']
555 | , 'KEYT', '', $memoryKV ? 'ENGINE = MEMORY' : '');
556 | }
557 |
558 | public function getPK($query, $pk = null)
559 | {
560 | try {
561 | $pkResult = [];
562 | if ($this->parent->isQuery($query)) {
563 | $q = $this->parent->toMeta($query);
564 | foreach ($q as $item) {
565 | if (in_array('primary_key', $item['flags'])) {
566 | $pkResult[] = $item['name'];
567 | //break;
568 | }
569 | }
570 | } else {
571 | // get the pk by table name
572 | $r = $this->getDefTableKeys($query, true, 'PRIMARY KEY');
573 | if (count($r) >= 1) {
574 | foreach ($r as $key => $item) {
575 | $pkResult[] = $key;
576 | }
577 | } else {
578 | $pkResult[] = '??nopk??';
579 | }
580 | }
581 | $pkAsArray = (is_array($pk)) ? $pk : array($pk);
582 | return count($pkResult) === 0 ? $pkAsArray : $pkResult;
583 | } catch (Exception $ex) {
584 | return false;
585 | }
586 | }
587 |
588 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
589 | {
590 | $columns = [];
591 | /** @var array $indexArr =array(["Table"=>'',"Non_unique"=>0,"Key_name"=>'',"Seq_in_index"=>0
592 | * ,"Column_name"=>'',"Collation"=>'',"Cardinality"=>0,"Sub_part"=>0,"Packed"=>'',"Null"=>''
593 | * ,"Index_type"=>'',"Comment"=>'',"Index_comment"=>'',"Visible"=>'',"Expression"=>''])
594 | */
595 | $indexArr = $this->parent->runRawQuery('show index from ' . $table);
596 | foreach ($indexArr as $item) {
597 | if (strtoupper($item['Key_name']) === 'PRIMARY') {
598 | $type = 'PRIMARY KEY';
599 | } elseif ($item['Non_unique'] != 0) {
600 | $type = 'KEY';
601 | } else {
602 | $type = 'UNIQUE KEY';
603 | }
604 | if ($filter === null || $filter === $type) {
605 | if (!isset($columns[$item['Column_name']])) {
606 | if ($returnSimple) {
607 | $columns[$item['Column_name']] = $type; // [$item['Seq_in_index']-1]
608 | } else {
609 | $columns[$item['Column_name']] = PdoOne::newColFK($type, '', ''); //[$item['Seq_in_index']-1]
610 | }
611 | }
612 | }
613 | }
614 | return $columns; //$this->parent->filterKey($filter,$columns,$returnSimple);
615 | }
616 |
617 | public function db($dbname): string
618 | {
619 | return 'use ' . $dbname;
620 | }
621 | }
622 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_Mysql.php:
--------------------------------------------------------------------------------
1 | parent = $parent;
36 | }
37 |
38 | public function construct($charset, $config): string
39 | {
40 | $this->parent->database_delimiter0 = '`';
41 | $this->parent->database_delimiter1 = '`';
42 | $this->parent->database_identityName = 'auto_increment';
43 | $charset = ($charset == null) ? 'utf8' : $charset;
44 | PdoOne::$dateFormat = 'Y-m-d';
45 | PdoOne::$dateTimeFormat = 'Y-m-d H:i:s';
46 | PdoOne::$dateTimeMicroFormat = 'Y-m-d H:i:s.u';
47 | PdoOne::$isoDateInput = 'Y-m-d';
48 | PdoOne::$isoDateInputTime = 'Y-m-d H:i:s';
49 | PdoOne::$isoDateInputTimeMs = 'Y-m-d H:i:s.u';
50 | $this->parent->isOpen = false;
51 | return $charset;
52 | }
53 |
54 | public function connect($cs, $alterSession = false): void
55 | {
56 | $this->parent->conn1
57 | = new PDO("{$this->parent->databaseType}:host={$this->parent->server};dbname={$this->parent->db}$cs",
58 | $this->parent->user, $this->parent->pwd);
59 | $this->parent->user = '';
60 | $this->parent->pwd = '';
61 | $this->parent->conn1->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
62 | }
63 |
64 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = []): bool
65 | {
66 | $keys = array_keys($arguments);
67 | $outputFields = '';
68 | $argList = '';
69 | if (count($keys) > 0) {
70 | foreach ($arguments as $k => $v) {
71 | /** @noinspection TypeUnsafeArraySearchInspection */
72 | if (in_array($k, $outputColumns)) {
73 | $argList .= "@$k,";
74 | $outputFields .= "@$k as `$k`,";
75 | $stmt = $this->parent->prepare("set @$k=:$k");
76 | $stmt->bindParam($k, $v, $this->parent->getType($v));
77 | $stmt->execute();
78 | } else {
79 | $argList .= ":$k,";
80 | }
81 | }
82 | $argList = trim($argList, ','); // remove the trail comma
83 | $outputFields = trim($outputFields, ',');
84 | }
85 | $stmt = $this->parent->prepare("call $procName($argList)");
86 | foreach ($arguments as $k => $v) {
87 | /** @noinspection TypeUnsafeArraySearchInspection */
88 | if (!in_array($k, $outputColumns)) {
89 | $stmt->bindParam($k, $arguments[$k], $this->parent->getType($arguments[$k]));
90 | }
91 | }
92 | $stmt->execute();
93 | if ($outputFields !== '') {
94 | $stmt = $this->parent->prepare("select $outputFields");
95 | $stmt->execute();
96 | $var = $stmt->fetch(PDO::FETCH_ASSOC);
97 | $arguments = array_merge($arguments, $var);
98 | }
99 | $stmt = null;
100 | return true;
101 | }
102 |
103 | public function truncate(string $tableName, string $extra, bool $force)
104 | {
105 | if (!$force) {
106 | $sql = 'truncate table ' . $this->parent->addDelimiter($tableName) . " $extra";
107 | return $this->parent->runRawQuery($sql);
108 | }
109 | $sql = "SET FOREIGN_KEY_CHECKS = 0;
110 | TRUNCATE " . $this->parent->addDelimiter($tableName) . " $extra;
111 | SET FOREIGN_KEY_CHECKS = 1;";
112 | return $this->parent->runMultipleRawQuery($sql, true);
113 | }
114 |
115 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = '')
116 | {
117 | $sql = "ALTER TABLE " . $this->parent->addDelimiter($tableName) . " AUTO_INCREMENT = $newValue";
118 | return $this->parent->runRawQuery($sql);
119 | }
120 |
121 | /**
122 | * @param string $table
123 | * @param bool $onlyDescription
124 | *
125 | * @return array|string|null ['table','engine','schema','collation','description']
126 | * @throws Exception
127 | */
128 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
129 | {
130 | $query = "SELECT table_name as `table`,engine as `engine`, table_schema as `schema`," .
131 | " table_collation as `collation`, table_comment as `description` " .
132 | "FROM information_schema.tables WHERE table_schema = ? and table_name=?";
133 | $result = $this->parent->runRawQuery($query, [$this->parent->db, $table]);
134 | $result= array_change_key_case($result[0],0); // CASE_LOWER
135 | if ($onlyDescription) {
136 | return $result['description'];
137 | }
138 | return $result;
139 | }
140 |
141 | /**
142 | * @param string $table
143 | * @return array ['Field','Type','Null','Key','Default','Extra']
144 | * @throws Exception
145 | */
146 | public function getDefTable(string $table): array
147 | {
148 | $defArray = $this->parent->runRawQuery('show columns from ' . $table, []);
149 | $result = [];
150 | foreach ($defArray as $col) {
151 | /*if ($col['Key'] === 'PRI') {
152 | $pk = $col['Field'];
153 | }*/
154 |
155 | $type = $col['Type'];
156 | $type = str_replace('int(11)', 'int', $type);
157 | $value = $type;
158 | $value .= ($col['Null'] === 'NO') ? ' not null' : '';
159 | if ($col['Default'] === 'CURRENT_TIMESTAMP') {
160 | $value .= ' default CURRENT_TIMESTAMP';
161 | } else {
162 | $value .= ($col['Default']) ? ' default ' . PdoOne::addParenthesis($col['Default'], "'", "'") : '';
163 | }
164 | $col['Extra'] = str_replace('DEFAULT_GENERATED ', '', $col['Extra']);
165 | $value .= ($col['Extra']) ? ' ' . $col['Extra'] : '';
166 | $result[$col['Field']] = $value;
167 | }
168 | return $result;
169 | }
170 |
171 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
172 | {
173 | $columns = [];
174 | /** @var array $result =array(["CONSTRAINT_NAME"=>'',"COLUMN_NAME"=>'',"REFERENCED_TABLE_NAME"=>''
175 | * ,"REFERENCED_COLUMN_NAME"=>'',"UPDATE_RULE"=>'',"DELETE_RULE"=>''])
176 | */
177 | $fkArr = $this->parent->select('k.CONSTRAINT_NAME,k.COLUMN_NAME,k.REFERENCED_TABLE_NAME
178 | ,k.REFERENCED_COLUMN_NAME,c.UPDATE_RULE,c.DELETE_RULE,k.POSITION_IN_UNIQUE_CONSTRAINT')
179 | ->from('INFORMATION_SCHEMA.KEY_COLUMN_USAGE k')
180 | ->innerjoin('information_schema.REFERENTIAL_CONSTRAINTS c
181 | ON k.referenced_table_schema=c.CONSTRAINT_SCHEMA AND k.CONSTRAINT_NAME=c.CONSTRAINT_NAME')
182 | ->where('k.TABLE_SCHEMA=? AND k.TABLE_NAME = ?', [$this->parent->db, $table])
183 | ->order('k.POSITION_IN_UNIQUE_CONSTRAINT,k.COLUMN_NAME')->toList(); // to sort by the position is important.
184 | foreach ($fkArr as $k => $item) {
185 | if ($item['POSITION_IN_UNIQUE_CONSTRAINT'] == 2) {
186 | // constraint uses two columns
187 | $find = null;
188 | foreach ($fkArr as $k2 => $item2) {
189 | if ($item['CONSTRAINT_NAME'] === $item2['CONSTRAINT_NAME']) {
190 | $find = $k2;
191 | break;
192 | }
193 | }
194 | if ($find !== null) {
195 | $fkArr[$find]['REFERENCED_COLUMN_NAME'] .= ',' . $item['REFERENCED_COLUMN_NAME'];
196 | $fkArr[$k] = null;
197 | //unset($fkArr[$k]);
198 | }
199 | }
200 | }
201 | foreach ($fkArr as $item) {
202 | if ($item !== null) {
203 | $rcn = $item['REFERENCED_COLUMN_NAME'];
204 | if (strpos($rcn, ',') !== false) {
205 | $tmp = explode(',', $rcn);
206 | $rcn = "`$tmp[0]`,`$tmp[1]`";
207 | } else {
208 | $rcn = "`$rcn`";
209 | }
210 | $txt = "FOREIGN KEY REFERENCES`{$item['REFERENCED_TABLE_NAME']}`($rcn)";
211 | $extra = '';
212 | if ($item['UPDATE_RULE'] && $item['UPDATE_RULE'] !== 'NO ACTION') {
213 | $extra .= ' ON UPDATE ' . $item['UPDATE_RULE'];
214 | }
215 | if ($item['DELETE_RULE'] && $item['DELETE_RULE'] !== 'NO ACTION') {
216 | $extra .= ' ON DELETE ' . $item['DELETE_RULE'];
217 | }
218 | if ($returnSimple) {
219 | $columns[$item['COLUMN_NAME']] = $txt . $extra;
220 | } else {
221 | $columns[$item['COLUMN_NAME']] = PdoOne::newColFK('FOREIGN KEY', $item['REFERENCED_COLUMN_NAME'],
222 | $item['REFERENCED_TABLE_NAME'], $extra, $item['CONSTRAINT_NAME']);
223 | $columns[PdoOne::addPrefix($item['COLUMN_NAME'])] = PdoOne::newColFK('MANYTOONE', $item['REFERENCED_COLUMN_NAME'],
224 | $item['REFERENCED_TABLE_NAME'], $extra, $item['CONSTRAINT_NAME']);
225 | }
226 | }
227 | }
228 | if ($assocArray) {
229 | return $columns;
230 | }
231 | return $this->parent->filterKey($filter, $columns, $returnSimple);
232 | }
233 |
234 | public function typeDict($row, bool $default = true): string
235 | {
236 | $type = @$row['native_type'];
237 | switch ($type) {
238 | case 'VAR_STRING':
239 | case 'BLOB':
240 | case 'TINY_BLOB':
241 | case 'MEDIUM_BLOB':
242 | case 'LONG_BLOB':
243 | case 'STRING':
244 | case 'GEOMETRY':
245 | case 'TIMESTAMP':
246 | case 'TIME':
247 | case 'DATE':
248 | case 'DATETIME':
249 | case 'NULL':
250 | return ($default) ? "''" : 'string';
251 | case 'LONG':
252 | case 'LONGLONG':
253 | case 'SHORT':
254 | case 'INT24':
255 | case 'TINY':
256 | case 'YEAR':
257 | return ($default) ? '0' : 'int';
258 | case 'DECIMAL':
259 | case 'DOUBLE':
260 | case 'FLOAT':
261 | case 'NEWDECIMAL':
262 | return ($default) ? '0.0' : 'float';
263 | default:
264 | return '???' . $type;
265 | }
266 | }
267 |
268 | public function objectExist(string $type = 'table'): ?string
269 | {
270 | switch ($type) {
271 | case 'table':
272 | $query
273 | = "SELECT * FROM information_schema.tables where table_schema='{$this->parent->db}' and table_name=?";
274 | break;
275 | case 'function':
276 | $query
277 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where
278 | ROUTINE_SCHEMA='{$this->parent->db}'
279 | and ROUTINE_NAME=?
280 | and ROUTINE_TYPE='FUNCTION'";
281 | break;
282 | case 'procedure':
283 | $query
284 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where
285 | ROUTINE_SCHEMA='{$this->parent->db}'
286 | and ROUTINE_NAME=?
287 | and ROUTINE_TYPE='PROCEDURE'";
288 | break;
289 | default:
290 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
291 | '');
292 | return null;
293 | }
294 | return $query;
295 | }
296 |
297 | public function objectList(string $type = 'table', bool $onlyName = false)
298 | {
299 | switch ($type) {
300 | case 'table':
301 | $query
302 | = "SELECT * FROM information_schema.tables where table_schema='{$this->parent->db}' and table_type='BASE TABLE'";
303 | if ($onlyName) {
304 | $query = str_replace('*', 'table_name as name', $query);
305 | }
306 | break;
307 | case 'function':
308 | $query
309 | = "SELECT * FROM INFORMATION_SCHEMA.ROUTINES where ROUTINE_SCHEMA='{$this->parent->db}'";
310 | if ($onlyName) {
311 | $query = str_replace('*', 'routine_name', $query);
312 | }
313 | break;
314 | default:
315 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}",
316 | '');
317 | return null;
318 | }
319 | return $query;
320 | }
321 |
322 | public function columnTable($tableName): string
323 | {
324 | return "SELECT column_name colname
325 | ,data_type coltype
326 | ,character_maximum_length colsize
327 | ,numeric_precision colpres
328 | ,numeric_scale colscale
329 | ,if(column_key='PRI',1,0) iskey
330 | ,if(extra='auto_increment',1,0) isidentity
331 | ,if(is_nullable='NO',1,0) isnullable
332 | FROM information_schema.columns
333 | where table_schema='{$this->parent->db}' and table_name='$tableName'";
334 | }
335 |
336 | public function foreignKeyTable($tableName): string
337 | {
338 | return "SELECT
339 | column_name collocal,
340 | REFERENCED_TABLE_NAME tablerem,
341 | REFERENCED_COLUMN_NAME colrem,
342 | constraint_name fk_name
343 | FROM information_schema.KEY_COLUMN_USAGE
344 | where table_name='$tableName' and constraint_schema='{$this->parent->db}'
345 | and referenced_table_name is not null;";
346 | }
347 |
348 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
349 | {
350 | $ok = $this->parent->createTable($tableSequence, [
351 | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
352 | 'stub' => "char(1) NOT NULL DEFAULT ''",
353 | ], [
354 | 'id' => 'PRIMARY KEY',
355 | 'stub' => 'UNIQUE KEY'
356 | ], '', 'ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8');
357 | if (!$ok) {
358 | $this->parent->throwError("Unable to create table $tableSequence", '');
359 | return [''];
360 | }
361 | $ok = $this->parent->insert($tableSequence, ['stub' => 'a']);
362 | if (!$ok) {
363 | $this->parent->throwError("Unable to insert in table $tableSequence", '');
364 | return [''];
365 | }
366 | $sql = 'SET GLOBAL log_bin_trust_function_creators = ?';
367 | try {
368 | $this->parent->runRawQuery($sql, [1]);
369 | } catch (Exception $ex) {
370 | // it crashed with "Wrong COM_STMT_PREPARE response size. Received 7"
371 | // however, the operation is correctly executed, so let's do nothing.
372 | }
373 | if ($method === 'snowflake') {
374 | $sql = "CREATE FUNCTION `next_$tableSequence`(node integer) RETURNS BIGINT(20)
375 | MODIFIES SQL DATA
376 | NOT DETERMINISTIC
377 | BEGIN
378 | DECLARE epoch BIGINT(20);
379 | DECLARE current_ms BIGINT(20);
380 | DECLARE incr BIGINT(20);
381 | SET current_ms = round(UNIX_TIMESTAMP(CURTIME(4)) * 1000);
382 | SET epoch = 1459440000000;
383 | REPLACE INTO $tableSequence (stub) VALUES ('a');
384 | SELECT LAST_INSERT_ID() INTO incr;
385 | RETURN (current_ms - epoch) << 22 | (node << 12) | (incr % 4096);
386 | END";
387 | } else {
388 | $sql = "CREATE DEFINER=CURRENT_USER FUNCTION `next_$tableSequence`(node integer) RETURNS BIGINT(20)
389 | MODIFIES SQL DATA
390 | NOT DETERMINISTIC
391 | BEGIN
392 | DECLARE incr BIGINT(20);
393 | REPLACE INTO $tableSequence (stub) VALUES ('a');
394 | SELECT LAST_INSERT_ID() INTO incr;
395 | RETURN incr;
396 | END";
397 | }
398 | return [$sql];
399 | }
400 |
401 | /**
402 | * @param string $procedureName
403 | * @param string|array $arguments
404 | * @param string $body
405 | * @param string $extra
406 | * @return string
407 | */
408 | public function createProcedure(string $procedureName, $arguments = '', string $body = '', string $extra = ''): string
409 | {
410 | if (is_array($arguments)) {
411 | $sqlArgs = '';
412 | foreach ($arguments as $k => $v) {
413 | if (is_array($v)) {
414 | if (count($v) > 2) {
415 | $sqlArgs .= "$v[0] $v[1] $v[2],";
416 | } else {
417 | $sqlArgs .= "in $v[0] $v[1],";
418 | }
419 | } else {
420 | $sqlArgs .= "in $k $v,";
421 | }
422 | }
423 | $sqlArgs = trim($sqlArgs, ',');
424 | } else {
425 | $sqlArgs = $arguments;
426 | }
427 | $sql = "CREATE PROCEDURE `$procedureName` ($sqlArgs) $extra\n";
428 | $sql .= "BEGIN\n$body\nEND";
429 | return $sql;
430 | }
431 |
432 | public function getSequence($sequenceName): string
433 | {
434 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
435 | return "select next_$sequenceName({$this->parent->nodeId}) id";
436 | }
437 |
438 | public function translateExtra($universalExtra): string
439 | {
440 | /** @noinspection DegradedSwitchInspection */
441 | switch ($universalExtra) {
442 | case 'autonumeric':
443 | $sqlExtra = 'AUTO_INCREMENT';
444 | break;
445 | default:
446 | $sqlExtra = $universalExtra;
447 | }
448 | return $sqlExtra;
449 | }
450 |
451 | public function translateType($universalType, $len = null): string
452 | {
453 | switch ($universalType) {
454 | case 'int':
455 | $sqlType = "int";
456 | break;
457 | case 'long':
458 | $sqlType = "long";
459 | break;
460 | case 'decimal':
461 | $sqlType = "decimal($len) ";
462 | break;
463 | case 'bool':
464 | $sqlType = "char(1)";
465 | break;
466 | case 'date':
467 | $sqlType = "date";
468 | break;
469 | case 'datetime':
470 | $sqlType = "datetime";
471 | break;
472 | case 'timestamp':
473 | $sqlType = "timestamp";
474 | break;
475 | case 'string':
476 | default:
477 | $sqlType = "varchar($len) ";
478 | break;
479 | }
480 | return $sqlType;
481 | }
482 |
483 | public function createTable(
484 | string $tableName,
485 | array $definition,
486 | $primaryKey = null,
487 | string $extra = '',
488 | string $extraOutside = ''
489 | ): string
490 | {
491 | $extraOutside = ($extraOutside === '') ? "ENGINE=InnoDB DEFAULT CHARSET={$this->parent->charset};"
492 | : $extraOutside;
493 | $sql = "CREATE TABLE `$tableName` (";
494 | foreach ($definition as $key => $type) {
495 | $sql .= "`$key` $type,";
496 | }
497 | if ($primaryKey) {
498 | if (is_array($primaryKey)) {
499 | $hasPK = false;
500 | foreach ($primaryKey as $key => $value) {
501 | $valueKey = (is_array($value)) ? reset($value) : $value;
502 | $p0 = stripos($valueKey . ' ', 'KEY ');
503 | if ($p0 === false) {
504 | trigger_error('createTable: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
505 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
506 | break;
507 | }
508 | $type = strtoupper(trim(substr($valueKey, 0, $p0)));
509 | $value = substr($valueKey, $p0 + 4);
510 | switch ($type) {
511 | case 'PRIMARY':
512 | if (!$hasPK) {
513 | $sql .= "PRIMARY KEY (`$key`*pk*) $value,";
514 | $hasPK = true;
515 | } else {
516 | $sql = str_replace('*pk*', ",`$key`", $sql);
517 | }
518 | break;
519 | case '':
520 | $sql .= "KEY `{$tableName}_{$key}_idx` (`$key`) $value,";
521 | break;
522 | case 'UNIQUE':
523 | $sql .= "UNIQUE KEY `{$tableName}_{$key}_idx` (`$key`) $value,";
524 | break;
525 | default:
526 | trigger_error("createTable: [$type KEY] not defined");
527 | break;
528 | }
529 | }
530 | $sql = str_replace('*pk*', '', $sql);
531 | $sql = rtrim($sql, ',');
532 | } else {
533 | $sql .= " PRIMARY KEY(`$primaryKey`) ";
534 | }
535 | } else {
536 | $sql = substr($sql, 0, -1);
537 | }
538 | $sql .= "$extra ) " . $extraOutside;
539 | return $sql;
540 | }
541 |
542 | public function addColumn(string $tableName, array $definition): string
543 | {
544 | $sql = "ALTER TABLE `$tableName`";
545 | foreach ($definition as $key => $type) {
546 | $sql .= "ADD COLUMN `$key` $type,";
547 | }
548 | return rtrim($sql, ',');
549 | }
550 |
551 | public function deleteColumn(string $tableName, $columnName): string
552 | {
553 | $sql = "ALTER TABLE `$tableName`";
554 | if (!is_array($columnName)) {
555 | $columnName = [$columnName];
556 | }
557 | foreach ($columnName as $c) {
558 | $sql .= "DROP COLUMN $c,";
559 | }
560 | return rtrim($sql, ',');
561 | }
562 |
563 | /** @noinspection SqlResolve */
564 | public function createFK(string $tableName, array $foreignKeys): string
565 | {
566 | $sql = '';
567 | foreach ($foreignKeys as $key => $value) {
568 | $p0 = stripos($value . ' ', 'KEY ');
569 | if ($p0 === false) {
570 | trigger_error('createTable: Key with a wrong syntax: "FOREIGN KEY.." ');
571 | break;
572 | }
573 | $type = strtoupper(trim(substr($value, 0, $p0)));
574 | $value = substr($value, $p0 + 4);
575 | if ($type === 'FOREIGN') {
576 | $sql .= "ALTER TABLE `$tableName` ADD CONSTRAINT `fk_{$tableName}_$key` FOREIGN KEY(`$key`) $value;";
577 | }
578 | }
579 | return $sql;
580 | }
581 |
582 | public function createIndex(string $tableName, array $indexesAndDef): string
583 | {
584 | $sql = '';
585 | foreach ($indexesAndDef as $key => $typeIndex) {
586 | $sql .= "ALTER TABLE `$tableName` ADD $typeIndex `idx_{$tableName}_$key` (`$key`) ;";
587 | }
588 | return $sql;
589 | }
590 |
591 | public function limit(?int $first, ?int $second): string
592 | {
593 | return $second === null ? ' limit ' . $first : " limit $first,$second";
594 | }
595 |
596 | public function now(): string
597 | {
598 | return 'select NOW() as NOW';
599 | }
600 |
601 | public function createTableKV($tableKV, $memoryKV = false): string
602 | {
603 | return $this->createTable($tableKV
604 | , ['KEYT' => 'VARCHAR(256)', 'VALUE' => 'MEDIUMTEXT', 'TIMESTAMP' => 'BIGINT']
605 | , 'KEYT', '', $memoryKV ? 'ENGINE = MEMORY' : '');
606 | }
607 |
608 | public function getPK($query, $pk = null)
609 | {
610 | try {
611 | $pkResult = [];
612 | if ($this->parent->isQuery($query)) {
613 | $q = $this->parent->toMeta($query);
614 | foreach ($q as $item) {
615 | if (in_array('primary_key', $item['flags'])) {
616 | $pkResult[] = $item['name'];
617 | //break;
618 | }
619 | }
620 | } else {
621 | // get the pk by table name
622 | $r = $this->getDefTableKeys($query, true, 'PRIMARY KEY');
623 | if (count($r) >= 1) {
624 | foreach ($r as $key => $item) {
625 | $pkResult[] = $key;
626 | }
627 | } else {
628 | $pkResult[] = '??nopk??';
629 | }
630 | }
631 | $pkAsArray = (is_array($pk)) ? $pk : [$pk];
632 | return count($pkResult) === 0 ? $pkAsArray : $pkResult;
633 | } catch (Exception $ex) {
634 | return false;
635 | }
636 | }
637 |
638 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
639 | {
640 | $columns = [];
641 | /** @var array $indexArr =array(["Table"=>'',"Non_unique"=>0,"Key_name"=>'',"Seq_in_index"=>0
642 | * ,"Column_name"=>'',"Collation"=>'',"Cardinality"=>0,"Sub_part"=>0,"Packed"=>'',"Null"=>''
643 | * ,"Index_type"=>'',"Comment"=>'',"Index_comment"=>'',"Visible"=>'',"Expression"=>''])
644 | */
645 | $indexArr = $this->parent->runRawQuery('show index from ' . $table);
646 | foreach ($indexArr as $item) {
647 | if (strtoupper($item['Key_name']) === 'PRIMARY') {
648 | $type = 'PRIMARY KEY';
649 | } elseif ($item['Non_unique'] != 0) {
650 | $type = 'KEY';
651 | } else {
652 | $type = 'UNIQUE KEY';
653 | }
654 | if ($filter === null || $filter === $type) {
655 | if (!isset($columns[$item['Column_name']])) {
656 | if ($returnSimple) {
657 | $columns[$item['Column_name']] = $type; // [$item['Seq_in_index']-1]
658 | } else {
659 | $columns[$item['Column_name']] = PdoOne::newColFK($type, '', ''); //[$item['Seq_in_index']-1]
660 | }
661 | }
662 | }
663 | }
664 | return $columns; //$this->parent->filterKey($filter,$columns,$returnSimple);
665 | }
666 |
667 | public function db($dbname): string
668 | {
669 | return 'use ' . $dbname;
670 | }
671 | }
672 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_Sqlsrv.php:
--------------------------------------------------------------------------------
1 | parent = $parent;
40 | }
41 |
42 | public function construct($charset, $config): string
43 | {
44 | $this->parent->database_delimiter0 = '[';
45 | $this->parent->database_delimiter1 = ']';
46 | $this->parent->database_identityName = 'IDENTITY';
47 | PdoOne::$isoDate = 'Y-m-d';
48 | PdoOne::$isoDateTime = 'Y-m-d H:i:s';
49 | PdoOne::$isoDateTimeMs = 'Y-m-d H:i:s.u';
50 | PdoOne::$isoDateInput = 'Ymd';
51 | PdoOne::$isoDateInputTime = 'Ymd H:i:s';
52 | PdoOne::$isoDateInputTimeMs = 'Ymd H:i:s.u';
53 | $this->parent->isOpen = false;
54 | return '';
55 | }
56 |
57 | public function connect($cs, $alterSession = false): void
58 | {
59 | $this->parent->conn1 = new PDO("{$this->parent->databaseType}:server={$this->parent->server};" .
60 | "database={$this->parent->db}$cs", $this->parent->user, $this->parent->pwd);
61 | $this->parent->user = '';
62 | $this->parent->pwd = '';
63 | $this->parent->conn1->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true);
64 | }
65 |
66 | public function truncate(string $tableName, string $extra, bool $force)
67 | {
68 | if (!$force) {
69 | $sql = 'truncate table ' . $this->parent->addDelimiter($tableName) . " $extra";
70 | return $this->parent->runRawQuery($sql);
71 | }
72 | $sql = "DELETE FROM " . $this->parent->addDelimiter($tableName) . " $extra";
73 | return $this->parent->runRawQuery($sql);
74 | }
75 |
76 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = '')
77 | {
78 | $sql = "DBCC CHECKIDENT ('$tableName',RESEED, $newValue)";
79 | return $this->parent->runRawQuery($sql);
80 | }
81 |
82 | /**
83 | * @param string $table
84 | * @param false $onlyDescription
85 | *
86 | * @return array|bool|mixed|PDOStatement|null ['table','engine','schema','collation','description']
87 | * @throws Exception
88 | */
89 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
90 | {
91 | $query = "SELECT objects.name as [table],'' as [engine],schemas.name as [schema]
92 | ,'' as [collation],value description
93 | FROM sys.objects
94 | inner join sys.schemas on objects.schema_id=schemas.schema_id
95 | CROSS APPLY fn_listextendedproperty(default,
96 | 'SCHEMA', schema_name(objects.schema_id),
97 | 'TABLE', objects.name, null, null) ep
98 | WHERE sys.objects.name=?";
99 | $result = $this->parent->runRawQuery($query, [$table]);
100 | if ($onlyDescription) {
101 | return $result['description'];
102 | }
103 | return $result;
104 | }
105 |
106 | public function getDefTable(string $table): array
107 | {
108 | /** @var array $result =array(["name"=>'',"is_identity"=>0,"increment_value"=>0,"seed_value"=>0]) */
109 | $findIdentity =
110 | $this->parent->select('name,is_identity,increment_value,seed_value')->from('sys.identity_columns')
111 | ->where('OBJECT_NAME(object_id)=?', $table)->toList();
112 | $findIdentity = (!is_array($findIdentity)) ? [] : $findIdentity; // it's always an arry
113 | $defArray = $this->parent->select('COLUMN_NAME,IS_NULLABLE,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH
114 | ,NUMERIC_PRECISION,NUMERIC_SCALE,COLUMN_DEFAULT,IDENT_SEED(\'".$table."\') HASIDENTITY')
115 | ->from('INFORMATION_SCHEMA.COLUMNS')->where('TABLE_NAME = ?', $table)
116 | ->order('ORDINAL_POSITION')->toList();
117 | $result = [];
118 | foreach ($defArray as $col) {
119 | $value = self::sqlsrv_getType($col);
120 | $value .= ($col['IS_NULLABLE'] === 'NO') ? ' NOT NULL' : '';
121 | $value .= ($col['COLUMN_DEFAULT']) ? ' DEFAULT ' . $col['COLUMN_DEFAULT'] : '';
122 | $colName = $col['COLUMN_NAME'];
123 | foreach ($findIdentity as $fi) {
124 | if ($colName === $fi['name']) {
125 | $value .= " IDENTITY({$fi['seed_value']},{$fi['increment_value']})";
126 | break;
127 | }
128 | }
129 | $result[$colName] = $value;
130 | }
131 | return $result;
132 | }
133 |
134 | /**
135 | * It gets a column from INFORMATION_SCHEMA.COLUMNS and returns a type of the form type,type(size)
136 | * or type(size,size)
137 | *
138 | * @param array $col
139 | *
140 | * @return string
141 | */
142 | protected static function sqlsrv_getType($col): string
143 | {
144 | /** @var array $exclusion type of columns that don't use size */
145 | $exclusion = ['int', 'long', 'tinyint', 'year', 'bigint', 'bit', 'smallint', 'float', 'money'];
146 | if (in_array($col['DATA_TYPE'], $exclusion, true) !== false) {
147 | return $col['DATA_TYPE'];
148 | }
149 | if ($col['NUMERIC_SCALE']) {
150 | $result = "{$col['DATA_TYPE']}({$col['NUMERIC_PRECISION']},{$col['NUMERIC_SCALE']})";
151 | } elseif ($col['NUMERIC_PRECISION'] || $col['CHARACTER_MAXIMUM_LENGTH']) {
152 | $result = "{$col['DATA_TYPE']}(" . ($col['CHARACTER_MAXIMUM_LENGTH'] + $col['NUMERIC_PRECISION']) . ')';
153 | } else {
154 | $result = $col['DATA_TYPE'];
155 | }
156 | return $result;
157 | }
158 |
159 | /**
160 | * @param string $table
161 | * @param bool $returnSimple
162 | * @param string|null $filter
163 | * @return array
164 | * @throws Exception
165 | */
166 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
167 | {
168 | $columns = [];
169 | /** @var array $result =array(["IndexName"=>'',"ColumnName"=>'',"is_unique"=>0,"is_primary_key"=>0,"TYPE"=>0]) */
170 | $result =
171 | $this->parent->select('IndexName = ind.name,ColumnName = col.name,ind.is_unique,IND.is_primary_key,IND.TYPE')
172 | ->from('sys.indexes ind')
173 | ->innerjoin('sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id')
174 | ->innerjoin('sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id')
175 | ->where("OBJECT_NAME( ind.object_id)='$table'")
176 | ->order('ind.name, ind.index_id, ic.index_column_id')->toList();
177 | foreach ($result as $item) {
178 | if ($item['is_primary_key']) {
179 | $type = 'PRIMARY KEY';
180 | } elseif ($item['is_unique']) {
181 | $type = 'UNIQUE KEY';
182 | } else {
183 | $type = 'KEY';
184 | }
185 | if ($filter === null || $filter === $type) {
186 | if ($returnSimple) {
187 | $columns[$item['ColumnName']] = $type;
188 | } else {
189 | $columns[$item['ColumnName']] = PdoOne::newColFK($type, '', '');
190 | }
191 | }
192 | }
193 | return $columns; //$this->parent->filterKey($filter, $columns, $returnSimple);
194 | }
195 |
196 | /**
197 | * @param string $table
198 | * @param bool $returnSimple
199 | * @param string|null $filter
200 | * @param bool $assocArray
201 | * @return array
202 | * @throws Exception
203 | */
204 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
205 | {
206 | $columns = [];
207 | /** @var array $fkArr =array(["foreign_key_name"=>'',"referencing_table_name"=>'',"COLUMN_NAME"=>''
208 | * ,"referenced_table_name"=>'',"referenced_column_name"=>'',"referenced_schema_name"=>''
209 | * ,"update_referential_action_desc"=>'',"delete_referential_action_desc"=>''])
210 | */
211 | $fkArr = $this->parent->select('OBJECT_NAME(f.constraint_object_id) foreign_key_name
212 | ,OBJECT_NAME(f.parent_object_id) referencing_table_name
213 | ,COL_NAME(f.parent_object_id, f.parent_column_id) COLUMN_NAME
214 | ,OBJECT_NAME (f.referenced_object_id) referenced_table_name
215 | ,COL_NAME(f.referenced_object_id, f.referenced_column_id) referenced_column_name
216 | ,OBJECT_SCHEMA_NAME(f.referenced_object_id) referenced_schema_name
217 | , fk.update_referential_action_desc, fk.delete_referential_action_desc
218 | ,fk.name fk_name')
219 | ->from('sys.foreign_key_columns AS f')
220 | ->innerjoin('sys.foreign_keys as fk on fk.OBJECT_ID = f.constraint_object_id')
221 | ->where("OBJECT_NAME(f.parent_object_id)='$table'")
222 | ->order('COL_NAME(f.parent_object_id, f.parent_column_id)')->toList();
223 | foreach ($fkArr as $item) {
224 | $extra = ($item['update_referential_action_desc'] !== 'NO_ACTION') ? ' ON UPDATE ' .
225 | str_replace('_', ' ', $item['update_referential_action_desc']) : '';
226 | $extra .= ($item['delete_referential_action_desc'] !== 'NO_ACTION') ? ' ON DELETE ' .
227 | str_replace('_', ' ', $item['delete_referential_action_desc']) : '';
228 | //FOREIGN KEY REFERENCES TABLEREF(COLREF)
229 | if ($returnSimple) {
230 | $columns[$item['COLUMN_NAME']] =
231 | 'FOREIGN KEY REFERENCES ' . $this->parent->addQuote($item['referenced_table_name'])
232 | . '(' . $this->parent->addQuote($item['referenced_column_name']) . ')' . $extra;
233 | } else {
234 | $columns[$item['COLUMN_NAME']] = PdoOne::newColFK('FOREIGN KEY'
235 | , $item['referenced_column_name']
236 | , $item['referenced_table_name']
237 | , $extra
238 | , $item['fk_name']);
239 | $columns[PdoOne::addPrefix($item['COLUMN_NAME'])] = PdoOne::newColFK(
240 | 'MANYTOONE'
241 | , $item['referenced_column_name']
242 | , $item['referenced_table_name']
243 | , $extra
244 | , $item['fk_name']);
245 | }
246 | }
247 | if ($assocArray) {
248 | return $columns;
249 | }
250 | return $this->parent->filterKey($filter, $columns, $returnSimple);
251 | }
252 |
253 | function typeDict($row, bool $default = true): string
254 | {
255 | $type = @$row['sqlsrv:decl_type'];
256 | switch ($type) {
257 | case 'varchar':
258 | case 'nvarchar':
259 | case 'text':
260 | case 'ntext':
261 | case 'char':
262 | case 'nchar':
263 | case 'binary':
264 | case 'varbinary':
265 | case 'timestamp':
266 | case 'time':
267 | case 'date':
268 | case 'smalldatetime':
269 | case 'datetime2':
270 | case 'datetimeoffset':
271 | case 'datetime':
272 | case 'image':
273 | return ($default) ? "''" : 'string';
274 | case 'long':
275 | case 'tinyint':
276 | case 'int':
277 | case 'sql_variant':
278 | case 'int identity':
279 | case 'year':
280 | case 'bigint':
281 | case 'numeric':
282 | case 'bit':
283 | case 'smallint':
284 | return ($default) ? '0' : 'int';
285 | case 'decimal':
286 | case 'smallmoney':
287 | case 'money':
288 | case 'double':
289 | case 'real':
290 | case 'float':
291 | return ($default) ? '0.0' : 'float';
292 | default:
293 | return '???sqlsrv:' . $type;
294 | }
295 | }
296 |
297 | public function objectExist(string $type = 'table'): ?string
298 | {
299 | switch ($type) {
300 | case 'table':
301 | $query = "SELECT * FROM sys.objects where name=? and type_desc='USER_TABLE'";
302 | break;
303 | case 'function':
304 | $query = "SELECT * FROM sys.objects where name=? and type_desc='SQL_SCALAR_FUNCTION'";
305 | break;
306 | case 'sequence':
307 | $query = "SELECT * FROM sys.objects where name=? and type_desc='SEQUENCE_OBJECT'";
308 | break;
309 | case 'procedure':
310 | $query = "SELECT * FROM sys.objects where name=? and type_desc='SQL_STORED_PROCEDURE'";
311 | break;
312 | default:
313 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
314 | return null;
315 | }
316 | return $query;
317 | }
318 |
319 | public function objectList(string $type = 'table', bool $onlyName = false)
320 | {
321 | switch ($type) {
322 | case 'table':
323 | $query = "SELECT * FROM sys.objects where type_desc='USER_TABLE'";
324 | if ($onlyName) {
325 | $query = str_replace('*', 'name', $query);
326 | }
327 | break;
328 | case 'function':
329 | $query = "SELECT * FROM sys.objects where type_desc='CLR_SCALAR_FUNCTION'";
330 | if ($onlyName) {
331 | $query = str_replace('*', 'name', $query);
332 | }
333 | break;
334 | default:
335 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
336 | return null;
337 | }
338 | return $query;
339 | }
340 |
341 | public function columnTable($tableName): string
342 | {
343 | return "SELECT distinct col.name colname
344 | ,st.name coltype
345 | ,col.max_length colsize
346 | ,col.precision colpres
347 | ,col.scale colscale
348 | ,pk.is_primary_key iskey
349 | ,col.is_identity isidentity
350 | ,col.is_nullable isnullable
351 | FROM sys.COLUMNS col
352 | inner join sys.objects obj on obj.object_id=col.object_id
353 | inner join sys.types st on col.system_type_id=st.system_type_id
354 | left join sys.index_columns idx on obj.object_id=idx.object_id and col.column_id=idx.column_id
355 | left join sys.indexes pk on obj.object_id = pk.object_id and pk.index_id=idx.index_id and pk.is_primary_key=1
356 | where obj.name='$tableName'";
357 | }
358 |
359 | public function foreignKeyTable($tableName): string
360 | {
361 | return "SELECT col.name collocal
362 | ,objrem.name tablerem
363 | ,colrem.name colrem
364 | ,fks.name fk_name
365 | FROM sys.foreign_key_columns fk
366 | inner join sys.objects obj on obj.object_id=fk.parent_object_id
367 | inner join sys.COLUMNS col on obj.object_id=col.object_id and fk.parent_column_id=col.column_id
368 | inner join sys.types st on col.system_type_id=st.system_type_id
369 | inner join sys.objects objrem on objrem.object_id=fk.referenced_object_id
370 | inner join sys.COLUMNS colrem on fk.referenced_object_id=colrem.object_id and fk.referenced_column_id=colrem.column_id
371 | inner join sys.foreign_keys fks on fk.constraint_object_id=fks.object_id
372 | where obj.name='$tableName' ";
373 | }
374 |
375 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
376 | {
377 | $sql = [];
378 | $sql[] = "CREATE SEQUENCE [$tableSequence]
379 | START WITH 1
380 | INCREMENT BY 1;
381 | ";
382 | $sql[] = "create PROCEDURE next_$tableSequence
383 | @node int
384 | AS
385 | BEGIN
386 | -- Copyright Jorge Castro https://github.com/EFTEC/PdoOne
387 | SET NOCOUNT ON
388 | declare @return bigint
389 | declare @current_ms bigint
390 | declare @incr bigint
391 | -- 2018-01-01 is an arbitrary epoch
392 | set @current_ms=cast(DATEDIFF(s, '2018-01-01 00:00:00', GETDATE()) as bigint) *cast(1000 as bigint) + DATEPART(MILLISECOND,getutcdate())
393 | SELECT @incr= NEXT VALUE FOR $tableSequence
394 | -- current_ms << 22 | (node << 12) | (incr % 4096)
395 | set @return=(@current_ms*cast(4194304 as bigint)) + (@node *4096) + (@incr % 4096)
396 | select @return as id
397 | END";
398 | return $sql;
399 | }
400 |
401 | public function getSequence($sequenceName): string
402 | {
403 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
404 | return "exec next_$sequenceName {$this->parent->nodeId}";
405 | }
406 |
407 | public function translateExtra($universalExtra): string
408 | {
409 | /** @noinspection DegradedSwitchInspection */
410 | switch ($universalExtra) {
411 | case 'autonumeric':
412 | $sqlExtra = 'IDENTITY(1,1)';
413 | break;
414 | default:
415 | $sqlExtra = $universalExtra;
416 | }
417 | return $sqlExtra;
418 | }
419 |
420 | public function translateType($universalType, $len = null): string
421 | {
422 | switch ($universalType) {
423 | case 'int':
424 | $sqlType = "int";
425 | break;
426 | case 'long':
427 | $sqlType = "long";
428 | break;
429 | case 'decimal':
430 | $sqlType = "decimal($len) ";
431 | break;
432 | case 'bool':
433 | $sqlType = "char(1)";
434 | break;
435 | case 'date':
436 | $sqlType = "date";
437 | break;
438 | case 'datetime':
439 | $sqlType = "datetime";
440 | break;
441 | case 'timestamp':
442 | $sqlType = "timestamp";
443 | break;
444 | case 'string':
445 | default:
446 | $sqlType = "varchar($len) ";
447 | break;
448 | }
449 | return $sqlType;
450 | }
451 |
452 | public function createTable(string $tableName, array $definition, $primaryKey = null, string $extra = '', string $extraOutside = ''): string
453 | {
454 | $extraOutside = ($extraOutside === '') ? 'ON [PRIMARY]' : $extraOutside;
455 | $sql = "set nocount on;
456 | CREATE TABLE [$tableName] (";
457 | foreach ($definition as $key => $type) {
458 | $sql .= "[$key] $type,";
459 | }
460 | $sql = rtrim($sql, ',');
461 | $sql .= "$extra ) ON [PRIMARY]; ";
462 | if (!is_array($primaryKey)) {
463 | $sql .= "
464 | ALTER TABLE [$tableName] ADD CONSTRAINT
465 | PK_$tableName PRIMARY KEY CLUSTERED ([$primaryKey]) $extraOutside;";
466 | } else {
467 | $hasPK = false;
468 | foreach ($primaryKey as $key => $value) {
469 | $p0 = stripos($value . ' ', 'KEY ');
470 | if ($p0 === false) {
471 | trigger_error('createTable: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
472 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
473 | break;
474 | }
475 | $type = strtoupper(trim(substr($value, 0, $p0)));
476 | $value = substr($value, $p0 + 4);
477 | switch ($type) {
478 | case 'PRIMARY':
479 | if (!$hasPK) {
480 | $sql .= "ALTER TABLE [$tableName] ADD CONSTRAINT
481 | PK_$tableName PRIMARY KEY CLUSTERED ([$key]*pk*) $extraOutside;";
482 | $hasPK = true;
483 | } else {
484 | $sql = str_replace('*pk*', ",[$key]", $sql);
485 | }
486 | break;
487 | case '':
488 | $sql .= "CREATE INDEX {$tableName}_{$key}_idx ON $tableName ($key) $value;";
489 | break;
490 | case 'UNIQUE':
491 | $sql .= "CREATE UNIQUE INDEX {$tableName}_{$key}_idx ON $tableName ($key) $value;";
492 | break;
493 | case 'FOREIGN':
494 | $sql .= "ALTER TABLE $tableName ADD FOREIGN KEY ($key) $value;";
495 | break;
496 | default:
497 | trigger_error("createTable: [$type KEY] not defined");
498 | break;
499 | }
500 | }
501 | $sql = str_replace('*pk*', '', $sql);
502 | }
503 | return $sql;
504 | }
505 | public function addColumn(string $tableName,array $definition):string {
506 | $sql = "ALTER TABLE [$tableName] ";
507 | foreach ($definition as $key => $type) {
508 | $sql .= "ADD [$key] $type,";
509 | }
510 | return rtrim($sql,',');
511 | }
512 | public function deleteColumn(string $tableName, $columnName): string {
513 | $sql = '';
514 | if(!is_array($columnName)) {
515 | $columnName=[$columnName];
516 | }
517 | foreach($columnName as $c) {
518 | $sql = "ALTER TABLE [$tableName] ";
519 | $sql .= "DROP COLUMN $c;";
520 | }
521 | return rtrim($sql,';');
522 | }
523 |
524 | public function createFK(string $tableName, array $foreignKeys): ?string
525 | {
526 | $sql = '';
527 | foreach ($foreignKeys as $key => $value) {
528 | $p0 = stripos($value . ' ', 'KEY ');
529 | if ($p0 === false) {
530 | trigger_error('createFK: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
531 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
532 | return null;
533 | }
534 | $type = strtoupper(trim(substr($value, 0, $p0)));
535 | $value = substr($value, $p0 + 4);
536 | if ($type === 'FOREIGN') {
537 | $sql .= "ALTER TABLE $tableName ADD FOREIGN KEY ($key) $value;";
538 | }
539 | }
540 | return $sql;
541 | }
542 |
543 | public function createIndex(string $tableName, array $indexesAndDef): string
544 | {
545 | $sql = '';
546 | foreach ($indexesAndDef as $key => $typeIndex) {
547 | // CREATE INDEX index1 ON schema1.table1 (column1);
548 | $sql .= "CREATE INDEX [idx_{$tableName}_$key] ON [$tableName] ([$key]);";
549 | }
550 | return $sql;
551 | }
552 |
553 | public function limit(?int $first, ?int $second): string
554 | {
555 | //if (!$this->parent->order) {
556 | // $this->parent->throwError('limit without a sort', '');
557 | //}
558 | if ($second === null) {
559 | return " OFFSET 0 ROWS FETCH NEXT $first ROWS ONLY";
560 | }
561 | return " OFFSET $first ROWS FETCH NEXT $second ROWS ONLY";
562 | }
563 | public function now(): string
564 | {
565 | return "select GETDATE() as NOW";
566 | }
567 |
568 |
569 | /**
570 | *
571 | * @param string $tableKV
572 | * @param bool $memoryKV it requires a filegroup for memory.
573 | * @return string
574 | */
575 | public function createTableKV($tableKV, $memoryKV = false): string
576 | {
577 | return $this->createTable($tableKV
578 | , ['KEYT' => 'VARCHAR(256) NOT NULL', 'VALUE' => 'VARCHAR(MAX)', 'TIMESTAMP' => 'BIGINT']
579 | , 'KEYT', '', $memoryKV ? '(MEMORY_OPTIMIZED = ON,DURABILITY = SCHEMA_AND_DATA)' : '');
580 | }
581 |
582 | public function getPK($query, $pk = null)
583 | {
584 | try {
585 | $pkResult = [];
586 | if ($this->parent->isQuery($query)) {
587 | if (!$pk) {
588 | return 'SQLSRV: unable to find pk via query. Use the name of the table';
589 | }
590 | } else {
591 | // get the pk by table name
592 | $r = $this->getDefTableKeys($query, true, 'PRIMARY KEY');
593 | if (count($r) >= 1) {
594 | foreach ($r as $key => $item) {
595 | $pkResult[] = $key;
596 | }
597 | } else {
598 | $pkResult[] = '??nopk??';
599 | }
600 | }
601 | $pkAsArray = (is_array($pk)) ? $pk : array($pk);
602 | return count($pkResult) === 0 ? $pkAsArray : $pkResult;
603 | } catch (Exception $ex) {
604 | return false;
605 | }
606 | }
607 |
608 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = [])
609 | {
610 | $keys = array_keys($arguments);
611 | $argList = '';
612 | if (count($keys) > 0) {
613 | foreach ($arguments as $k => $v) {
614 | $argList .= ":$k,";
615 | }
616 | $argList = rtrim($argList, ','); // remove the trail comma
617 | }
618 | $sql = "{call $procName ($argList)}";
619 | $stmt = $this->parent->prepare($sql);
620 | foreach ($arguments as $k => $v) {
621 | if (in_array($k, $outputColumns, true)) {
622 | $stmt->bindParam(':' . $k, $arguments[$k], $this->parent->getType($v) | PDO::PARAM_INPUT_OUTPUT, 4000);
623 | } else {
624 | $stmt->bindParam(':' . $k, $arguments[$k], $this->parent->getType($v));
625 | }
626 | }
627 | $r = $stmt->execute();
628 | if ($r) {
629 | if ($stmt->columnCount() !== 0) {
630 | $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
631 | } else {
632 | $result = true;
633 | }
634 | } else {
635 | $result = false;
636 | }
637 | $stmt = null;
638 | return $result;
639 | }
640 |
641 | public function createProcedure(string $procedureName, $arguments = [], string $body = '', string $extra = ''): string
642 | {
643 | if (is_array($arguments)) {
644 | $sqlArgs = '';
645 | foreach ($arguments as $k => $v) {
646 | if (is_array($v)) {
647 | if (count($v) > 2) {
648 | // direction(in,output),name,type
649 | $sqlArgs .= "@$v[1] $v[2] $v[0],";
650 | } else {
651 | // name, type
652 | $sqlArgs .= "@$v[0] $v[1],";
653 | }
654 | } else {
655 | $sqlArgs .= "@$k $v,";
656 | }
657 | }
658 | $sqlArgs = trim($sqlArgs, ',');
659 | } else {
660 | $sqlArgs = $arguments;
661 | }
662 | $sql = "CREATE PROCEDURE [$procedureName] $sqlArgs $extra\n";
663 | $sql .= "AS\nBEGIN\n$body\nEND";
664 | return $sql;
665 | }
666 |
667 | public function db($dbname): string
668 | {
669 | return 'use ' . $dbname;
670 | }
671 | }
672 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_Oci.php:
--------------------------------------------------------------------------------
1 | true];
35 |
36 | /**
37 | * PdoOne_Mysql constructor.
38 | *
39 | * @param PdoOne $parent
40 | */
41 | public function __construct(PdoOne $parent)
42 | {
43 | $this->parent = $parent;
44 | }
45 |
46 | public function construct($charset, $config): string
47 | {
48 | $this->config = array_merge($this->config, $config);
49 | $this->parent->database_delimiter0 = '"';
50 | $this->parent->database_delimiter1 = '"';
51 | $this->parent->database_identityName = 'IDENTITY';
52 | // you should check the correct value at select * from nls_session_parameterswhere parameter = 'NLS_DATE_FORMAT';
53 | PdoOne::$dateFormat = 'Y-m-d';
54 | PdoOne::$dateTimeFormat = 'Y-m-d H:i:s';
55 | PdoOne::$dateTimeMicroFormat = 'Y-m-d H:i:s.u';
56 | PdoOne::$isoDateInput = 'Y-m-d';
57 | PdoOne::$isoDateInputTime = 'Y-m-d H:i:s';
58 | PdoOne::$isoDateInputTimeMs = 'Y-m-d H:i:s.u';
59 | $this->parent->isOpen = false;
60 | return '';
61 | }
62 |
63 | public function connect($cs, $alterSession = true): void
64 | {
65 | // dbname '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)))';
66 | // 'oci:dbname=localhost/XE', $user, $pass
67 | $cstring = "oci:dbname={$this->parent->server}";
68 | $this->parent->conn1 = new PDO($cstring, $this->parent->user, $this->parent->pwd);
69 | $this->parent->user = '';
70 | $this->parent->pwd = '';
71 | $this->parent->conn1->setAttribute(PDO::ATTR_AUTOCOMMIT, true);
72 | $this->parent->conn1->setAttribute(PDO::ATTR_PERSISTENT, true);
73 | //$this->parent->conn1->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // otherwise return "0.1" as "0,1"
74 | //$this->parent->conn1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
75 | if ($alterSession) {
76 | $this->parent->conn1->exec("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'
77 | NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'
78 | NLS_TIMESTAMP_TZ_FORMAT='YYYY-MM-DD HH24:MI:SS'
79 | NLS_NUMERIC_CHARACTERS = '.,'");
80 | }
81 | }
82 |
83 | public function truncate(string $tableName, string $extra, bool $force)
84 | {
85 | if (!$force) {
86 | $sql = 'truncate table ' . $this->parent->addDelimiter($tableName) . " $extra";
87 | return $this->parent->runRawQuery($sql);
88 | }
89 | $sql = "DELETE FROM " . $this->parent->addDelimiter($tableName) . " $extra";
90 | return $this->parent->runRawQuery($sql);
91 | }
92 |
93 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = '')
94 | {
95 | $sql = "ALTER TABLE \"$tableName\" MODIFY($column GENERATED AS IDENTITY (START WITH $newValue))";
96 | return $this->parent->runRawQuery($sql);
97 | }
98 |
99 | /**
100 | * @param string $table
101 | * @param false $onlyDescription
102 | *
103 | * @return array|bool|mixed|PDOStatement|null
104 | * @throws Exception
105 | */
106 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
107 | {
108 | $query = "SELECT
109 | ut.table_name
110 | ,'' as engine
111 | ,ut.owner schema
112 | ,default_collation collation
113 | ,uTC.COMMENTS description
114 | FROM
115 | all_tables ut,all_tab_comments utc
116 | WHERE ut.TABLE_NAME=? and ut.owner=?
117 | and ut.table_name=utc.table_name and ut.owner=utc.owner ";
118 | $result = $this->parent->runRawQuery($query, [$table, $this->parent->db]);
119 | if ($onlyDescription) {
120 | return $result['description'];
121 | }
122 | return $result;
123 | }
124 |
125 | public function getDefTable(string $table): array
126 | {
127 | /** @var array $result =array(["name"=>'',"is_identity"=>0,"increment_value"=>0,"seed_value"=>0]) */
128 | // throw new RuntimeException("no yet implemented");
129 | $raw = $this->parent->runRawQuery('select TO_CHAR(DBMS_METADATA.GET_DDL(\'TABLE\',?)) COL from dual', [$table]);
130 | if (!isset($raw[0]['COL'])) {
131 | return [];
132 | }
133 | $r = $raw[0]['COL'];
134 | $p0 = strpos($r, '(') + 1;
135 | $p1a = strpos($r, ' TABLESPACE ', $p0);
136 | $p1b = strpos($r, ' CONSTRAINT ', $p0);
137 | $p1c = strpos($r, ' USING ', $p0);
138 | $p1 = min($p1a, $p1b, $p1c);
139 | $rcut = trim(substr($r, $p0, $p1 - $p0), " \t\n\r\0\x0B,");
140 | $cols = explode(", \n", $rcut);
141 | $result = [];
142 | foreach ($cols as $v) {
143 | $key = explode(' ', $v . ' ', 2); // the last space avoid to return $key as an array with a single value.
144 | $result[PdoOne::removeDoubleQuotes($key[0])] = trim($key[1]);
145 | }
146 | return $result;
147 | }
148 |
149 | /**
150 | * It gets a column from INFORMATION_SCHEMA.COLUMNS and returns a type of the form type,type(size)
151 | * or type(size,size)
152 | *
153 | * @param array $col An associative array with the data of the column
154 | *
155 | * @return string
156 | * @noinspection PhpUnused
157 | */
158 | protected static function oci_getType($col): string
159 | {
160 | throw new RuntimeException("no yet implemented");
161 | /** @var array $exclusion type of columns that don't use size */
162 | $exclusion = ['int', 'long', 'tinyint', 'year', 'bigint', 'bit', 'smallint', 'float', 'money'];
163 | if (in_array($col['DATA_TYPE'], $exclusion, true) !== false) {
164 | return $col['DATA_TYPE'];
165 | }
166 | if ($col['NUMERIC_SCALE']) {
167 | $result = "{$col['DATA_TYPE']}({$col['NUMERIC_PRECISION']},{$col['NUMERIC_SCALE']})";
168 | } elseif ($col['NUMERIC_PRECISION'] || $col['CHARACTER_MAXIMUM_LENGTH']) {
169 | $result = "{$col['DATA_TYPE']}(" . ($col['CHARACTER_MAXIMUM_LENGTH'] + $col['NUMERIC_PRECISION']) . ')';
170 | } else {
171 | $result = $col['DATA_TYPE'];
172 | }
173 | return $result;
174 | }
175 |
176 | /**
177 | * @param string $table
178 | * @param bool $returnSimple
179 | * @param string|null $filter
180 | * @return array
181 | * @throws Exception
182 | */
183 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
184 | {
185 | $columns = [];
186 | /** @var array $result =array(["IndexName"=>'',"ColumnName"=>'',"is_unique"=>0,"is_primary_key"=>0,"TYPE"=>0]) */
187 | $pks = $this->getPK($table);
188 | $result =
189 | $this->parent->select('SELECT ALL_indexes.INDEX_NAME "IndexName",all_ind_columns.COLUMN_NAME "ColumnName",
190 | (CASE WHEN UNIQUENESS = \'UNIQUE\' THEN 1 ELSE 0 END) "is_unique",0 "is_primary_key",0 "TYPE"')
191 | ->from('ALL_indexes')
192 | ->innerjoin('all_ind_columns on ALL_indexes.index_name=all_ind_columns.index_name ')
193 | ->where("ALL_indexes.table_name='$table' and ALL_indexes.table_owner='{$this->parent->db}'")
194 | ->order('"IndexName"')->toList();
195 | foreach ($result as $k => $item) {
196 | if (in_array($item['ColumnName'], $pks, true)) {
197 | $type = 'PRIMARY KEY';
198 | $result[$k]['is_primary_key'] = 1;
199 | } elseif ($item['is_unique']) {
200 | $type = 'UNIQUE KEY';
201 | } else {
202 | $type = 'KEY';
203 | }
204 | if ($filter === null || $filter === $type) {
205 | if ($returnSimple) {
206 | $columns[$item['ColumnName']] = $type;
207 | } else {
208 | $columns[$item['ColumnName']] = PdoOne::newColFK($type, '', '');
209 | }
210 | }
211 | }
212 | return $columns; //$this->parent->filterKey($filter, $columns, $returnSimple);
213 | }
214 |
215 | /**
216 | * @param string $table
217 | * @param bool $returnSimple
218 | * @param string|null $filter
219 | * @param bool $assocArray
220 | * @return array
221 | * @throws Exception
222 | * todo: missing checking
223 | */
224 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
225 | {
226 | $columns = [];
227 | /** @var array $fkArr =array(["foreign_key_name"=>'',"referencing_table_name"=>'',"COLUMN_NAME"=>''
228 | * ,"referenced_table_name"=>'',"referenced_column_name"=>'',"referenced_schema_name"=>''
229 | * ,"update_referential_action_desc"=>'',"delete_referential_action_desc"=>''])
230 | */
231 | $fkArr = $this->parent->select('SELECT
232 | a.constraint_name "foreign_key_name",
233 | a.table_name "referencing_table_name",
234 | a.column_name "COLUMN_NAME",
235 | c_pk.table_name "referenced_table_name",
236 | b.column_name "referenced_column_name",
237 | c_pk.OWNER "referenced_schema_name",
238 | \'\' "update_referential_action_desc",
239 | c.DELETE_RULE "delete_referential_action_desc"
240 | FROM
241 | user_cons_columns a
242 | JOIN all_constraints c ON
243 | a.owner = c.owner
244 | AND a.constraint_name = c.constraint_name
245 | JOIN all_constraints c_pk ON
246 | c.r_owner = c_pk.owner
247 | AND c.r_constraint_name = c_pk.constraint_name
248 | LEFT JOIN USER_CONS_COLUMNS b ON
249 | b.OWNER = C_PK.owner
250 | AND b.CONSTRAINT_NAME = c_pk.CONSTRAINT_NAME')
251 | ->where("c.constraint_type = 'R' AND a.table_name=?", [$table])
252 | ->order('a.column_name')->toList();
253 | foreach ($fkArr as $item) {
254 | $extra = ($item['update_referential_action_desc'] !== 'NO_ACTION') ? ' ON UPDATE ' .
255 | str_replace('_', ' ', $item['update_referential_action_desc']) : '';
256 | $extra .= ($item['delete_referential_action_desc'] !== 'NO_ACTION') ? ' ON DELETE ' .
257 | str_replace('_', ' ', $item['delete_referential_action_desc']) : '';
258 | //FOREIGN KEY REFERENCES TABLEREF(COLREF)
259 | if ($returnSimple) {
260 | $columns[$item['COLUMN_NAME']] =
261 | 'FOREIGN KEY REFERENCES ' . $this->parent->addQuote($item['referenced_table_name'])
262 | . '(' . $this->parent->addQuote($item['referenced_column_name']) . ')' . $extra;
263 | } else {
264 | $columns[$item['COLUMN_NAME']] = PdoOne::newColFK('FOREIGN KEY'
265 | , $item['referenced_column_name']
266 | , $item['referenced_table_name']
267 | , $extra
268 | , $item['foreign_key_name']); // fk_name
269 | $columns[PdoOne::addPrefix($item['COLUMN_NAME'])] = PdoOne::newColFK(
270 | 'MANYTOONE'
271 | , $item['referenced_column_name']
272 | , $item['referenced_table_name']
273 | , $extra
274 | , $item['foreign_key_name']); // fk_name
275 | }
276 | }
277 | if ($assocArray) {
278 | return $columns;
279 | }
280 | return $this->parent->filterKey($filter, $columns, $returnSimple);
281 | }
282 |
283 | function typeDict($row, bool $default = true): string
284 | {
285 | $type = strtolower(@$row['oci:decl_type']);
286 | switch ($type) {
287 | case 'varchar':
288 | case 'varchar2':
289 | case 'nvarchar':
290 | case 'nvarchar2':
291 | case 'text':
292 | case 'ntext':
293 | case 'char':
294 | case 'nchar':
295 | case 'binary':
296 | case 'varbinary':
297 | case 'timestamp':
298 | case 'time':
299 | case 'date':
300 | case 'smalldatetime':
301 | case 'datetime2':
302 | case 'datetimeoffset':
303 | case 'datetime':
304 | case 'image':
305 | return ($default) ? "''" : 'string';
306 | case 'long':
307 | case 'tinyint':
308 | case 'number':
309 | case 'int':
310 | case 'sql_variant':
311 | case 'int identity':
312 | case 'year':
313 | case 'bigint':
314 | case 'numeric':
315 | case 'bit':
316 | case 'smallint':
317 | return ($default) ? '0' : 'int';
318 | case 'decimal':
319 | case 'smallmoney':
320 | case 'money':
321 | case 'double':
322 | case 'real':
323 | case 'float':
324 | return ($default) ? '0.0' : 'float';
325 | default:
326 | return '???oci:' . $type;
327 | }
328 | }
329 |
330 | public function objectExist(string $type = 'table'): ?string
331 | {
332 | switch ($type) {
333 | case 'table':
334 | $query = 'select * from all_tables where table_name=? and owner=?';
335 | break;
336 | case 'procedure':
337 | $query = "SELECT * FROM ALL_OBJECTS WHERE OBJECT_TYPE='PROCEDURE' and object_name=? and owner=?";
338 | break;
339 | case 'function':
340 | $query = "SELECT * FROM ALL_OBJECTS WHERE OBJECT_TYPE='FUNCTION' and object_name=? and owner=?";
341 | break;
342 | default:
343 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
344 | return null;
345 | }
346 | return $query;
347 | }
348 |
349 | public function objectList(string $type = 'table', bool $onlyName = false)
350 | {
351 | switch ($type) {
352 | case 'table':
353 | $query = "select * from user_tables";
354 | if ($onlyName) {
355 | $query = str_replace('*', 'table_name name', $query);
356 | }
357 | break;
358 | case 'function':
359 | $query = "select object_name from all_objects where owner = ? and object_type = 'FUNCTION';";
360 | if ($onlyName) {
361 | $query = str_replace('*', 'object_name name', $query);
362 | }
363 | break;
364 | default:
365 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
366 | return null;
367 | }
368 | return $query;
369 | }
370 |
371 | public function columnTable($tableName):string
372 | {
373 | throw new RuntimeException("no yet implemented");
374 | return "SELECT col.name colname
375 | ,st.name coltype
376 | ,col.max_length colsize
377 | ,col.precision colpres
378 | ,col.scale colscale
379 | ,pk.is_primary_key iskey
380 | ,col.is_identity isidentity
381 | ,col.is_nullable isnullable
382 | FROM sys.COLUMNS col
383 | inner join sys.objects obj on obj.object_id=col.object_id
384 | inner join sys.types st on col.system_type_id=st.system_type_id
385 | left join sys.index_columns idx on obj.object_id=idx.object_id and col.column_id=idx.column_id
386 | left join sys.indexes pk on obj.object_id = pk.object_id and pk.index_id=idx.index_id and pk.is_primary_key=1
387 | where obj.name='$tableName'";
388 | }
389 |
390 | public function foreignKeyTable($tableName): string
391 | {
392 | return "SELECT col.name collocal
393 | ,objrem.name tablerem
394 | ,colrem.name colrem
395 | ,fks.name fk_name
396 | FROM sys.foreign_key_columns fk
397 | inner join sys.objects obj on obj.object_id=fk.parent_object_id
398 | inner join sys.COLUMNS col on obj.object_id=col.object_id and fk.parent_column_id=col.column_id
399 | inner join sys.types st on col.system_type_id=st.system_type_id
400 | inner join sys.objects objrem on objrem.object_id=fk.referenced_object_id
401 | inner join sys.COLUMNS colrem on fk.referenced_object_id=colrem.object_id and fk.referenced_column_id=colrem.column_id
402 | inner join sys.foreign_keys fks on fk.constraint_object_id=fks.object_id
403 | where obj.name='$tableName' ";
404 | }
405 |
406 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
407 | {
408 | return ["CREATE SEQUENCE \"$tableSequence\"
409 | START WITH 1
410 | INCREMENT BY 1"];
411 | }
412 |
413 | public function getSequence($sequenceName): string
414 | {
415 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
416 | return "select \"$sequenceName\".nextval as \"id\" from dual";
417 | }
418 |
419 | public function translateExtra($universalExtra): string
420 | {
421 | /** @noinspection DegradedSwitchInspection */
422 | switch ($universalExtra) {
423 | case 'autonumeric':
424 | $sqlExtra = 'GENERATED BY DEFAULT AS IDENTITY';
425 | break;
426 | default:
427 | $sqlExtra = $universalExtra;
428 | }
429 | return $sqlExtra;
430 | }
431 |
432 | public function translateType($universalType, $len = null): string
433 | {
434 | switch ($universalType) {
435 | case 'int':
436 | $sqlType = "int";
437 | break;
438 | case 'long':
439 | $sqlType = "long";
440 | break;
441 | case 'decimal':
442 | $sqlType = "decimal($len) ";
443 | break;
444 | case 'bool':
445 | $sqlType = "char(1)";
446 | break;
447 | case 'date':
448 | case 'datetime':
449 | $sqlType = "date";
450 | break;
451 | case 'timestamp':
452 | $sqlType = "timestamp";
453 | break;
454 | case 'string':
455 | default:
456 | $sqlType = "varchar2($len) ";
457 | break;
458 | }
459 | return $sqlType;
460 | }
461 |
462 | public function createTable(string $tableName, array $definition, $primaryKey = null, string $extra = '', string $extraOutside = ''): string
463 | {
464 | $sql = "CREATE TABLE \"$tableName\" (";
465 | foreach ($definition as $key => $type) {
466 | $sql .= "\"$key\" $type,";
467 | }
468 | $sql = rtrim($sql, ',');
469 | $sql .= "$extra ); ";
470 | if ($primaryKey !== null) {
471 | if (!is_array($primaryKey)) {
472 | $pks = is_array($primaryKey) ? implode(',', $primaryKey) : $primaryKey;
473 | $sql .= "ALTER TABLE \"$tableName\" ADD (
474 | CONSTRAINT {$tableName}_PK PRIMARY KEY ($pks)
475 | ENABLE VALIDATE) $extraOutside;";
476 | } else {
477 | $hasPK = false;
478 | // ['field'=>'FOREIGN KEY REFERENCES TABLEREF(COLREF) ...]
479 | foreach ($primaryKey as $key => $value) {
480 | $p0 = stripos($value . ' ', 'KEY ');
481 | if ($p0 === false) {
482 | trigger_error('createTable: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
483 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
484 | break;
485 | }
486 | $type = strtoupper(trim(substr($value, 0, $p0)));
487 | $value = substr($value, $p0 + 4);
488 | switch ($type) {
489 | case 'PRIMARY':
490 | if (!$hasPK) {
491 | $sql .= "ALTER TABLE \"$tableName\" ADD ( CONSTRAINT PK_$tableName PRIMARY KEY(\"$key*pk*\") ENABLE VALIDATE);";
492 | $hasPK = true;
493 | } else {
494 | $sql = str_replace('*pk*', ",$key", $sql); // we add an extra primary key
495 | }
496 | break;
497 | case '':
498 | $sql .= "CREATE INDEX \"{$tableName}_{$key}_KEY\" ON $tableName (\"$key\") $value;";
499 | break;
500 | case 'UNIQUE':
501 | $sql .= "CREATE UNIQUE INDEX \"{$tableName}_{$key}_UK\" ON $tableName (\"$key\") $value;";
502 | break;
503 | case 'FOREIGN':
504 | $sql .= "ALTER TABLE \"$tableName\" ADD CONSTRAINT {$tableName}_{$key}_FK FOREIGN KEY (\"$key\") $value;";
505 | break;
506 | default:
507 | trigger_error("createTable: [$type KEY] not defined");
508 | break;
509 | }
510 | }
511 | $sql = str_replace('*pk*', '', $sql);
512 | }
513 | }
514 | return $sql;
515 | }
516 | public function addColumn(string $tableName,array $definition):string {
517 | $sql = "ALTER TABLE \"$tableName\" ADD (";
518 | foreach ($definition as $key => $type) {
519 | $sql .= "\"$key\" $type,";
520 | }
521 | return rtrim($sql,',').')';
522 | }
523 | public function deleteColumn(string $tableName, $columnName): string {
524 | if(!is_array($columnName)) {
525 | $columnName=[$columnName];
526 | }
527 | $sql = "ALTER TABLE \"$tableName\" DROP(";
528 | foreach($columnName as $c) {
529 | $sql .= "$c,";
530 | }
531 | return rtrim($sql,';').')';
532 | }
533 |
534 | public function createFK(string $tableName, array $foreignKeys): ?string
535 | {
536 | $sql = '';
537 | foreach ($foreignKeys as $key => $value) {
538 | $p0 = stripos($value . ' ', 'KEY ');
539 | if ($p0 === false) {
540 | trigger_error('createFK: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
541 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
542 | return null;
543 | }
544 | $type = strtoupper(trim(substr($value, 0, $p0)));
545 | $value = substr($value, $p0 + 4);
546 | if ($type === 'FOREIGN') {
547 | $sql .= "ALTER TABLE \"$tableName\" ADD CONSTRAINT {$tableName}_fk_$key FOREIGN KEY (\"$key\") $value;";
548 | }
549 | }
550 | return $sql;
551 | }
552 |
553 | public function createIndex(string $tableName, array $indexesAndDef): string
554 | {
555 | throw new RuntimeException('not tested');
556 | $sql = '';
557 | foreach ($indexesAndDef as $key => $typeIndex) {
558 | $sql .= "ALTER TABLE `$tableName` ADD $typeIndex `idx_{$tableName}_$key` (`$key`) ;";
559 | }
560 | return $sql;
561 | }
562 |
563 | /**
564 | * For 12c and higher.
565 | *
566 | * @param int|null $first
567 | * @param int|null $second
568 | * @return string
569 | */
570 | public function limit(?int $first, ?int $second): string
571 | {
572 | if ($second === null) {
573 | return " OFFSET 0 ROWS FETCH NEXT $first ROWS ONLY";
574 | }
575 | return " OFFSET $first ROWS FETCH NEXT $second ROWS ONLY";
576 | }
577 |
578 | /**
579 | * todo: not tested
580 | * @return string
581 | */
582 | public function now(): string
583 | {
584 | return "select TO_CHAR(SYSDATE, 'YYYY-DD-MM HH24:MI:SS') as NOW from dual";
585 | }
586 |
587 | /**
588 | *
589 | * @param string $tableKV
590 | * @param bool $memoryKV You must set this value in the tablespace and not here.
591 | * @return string
592 | */
593 | public function createTableKV($tableKV, $memoryKV = false): string
594 | {
595 | return $this->createTable($tableKV
596 | , ['KEYT' => 'VARCHAR2(256)', 'VALUE' => 'CLOB', 'TIMESTAMP' => 'BIGINT']
597 | , 'KEYT');
598 | }
599 |
600 | public function getPK($query, $pk = null)
601 | {
602 | try {
603 | $pkResult = [];
604 | if ($this->parent->isQuery($query)) {
605 | if (!$pk) {
606 | return 'OCI: unable to find pk via query. Use the name of the table';
607 | }
608 | } else {
609 | $q = "SELECT cols.column_name RESULT
610 | FROM all_constraints cons, all_cons_columns cols
611 | WHERE cols.table_name = ? and COLS.OWNER=?
612 | AND cons.constraint_type = 'P'
613 | AND cons.constraint_name = cols.constraint_name
614 | AND cons.owner = cols.owner";
615 | $r = $this->parent->runRawQuery($q, [$query, $this->parent->db]);
616 | if (count($r) >= 1) {
617 | foreach ($r as $item) {
618 | $pkResult[] = $item['RESULT'];
619 | }
620 | } else {
621 | $pkResult[] = '??nopk??';
622 | }
623 | }
624 | $pkAsArray = (is_array($pk)) ? $pk : array($pk);
625 | return count($pkResult) === 0 ? $pkAsArray : $pkResult;
626 | } catch (Exception $ex) {
627 | return false;
628 | }
629 | }
630 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = [])
631 | {
632 | // TODO: Implement callProcedure() method.
633 | throw new RuntimeException('not defined');
634 | return null;
635 | }
636 |
637 | public function createProcedure(string $procedureName, $arguments = [], string $body = '', string $extra = ''): string
638 | {
639 | if (is_array($arguments)) {
640 | $sqlArgs = '';
641 | foreach ($arguments as $k => $v) {
642 | if (is_array($v)) {
643 | if (count($v) > 2) {
644 | $sqlArgs .= "$v[1] $v[0] $v[2],";
645 | } else {
646 | $sqlArgs .= "$v[1] in $v[2],";
647 | }
648 | } else {
649 | $sqlArgs .= "$k in $v,";
650 | }
651 | }
652 | $sqlArgs = trim($sqlArgs, ',');
653 | } else {
654 | $sqlArgs = $arguments;
655 | }
656 | $sql = "CREATE OR REPLACE PROCEDURE \"$procedureName\" ($sqlArgs) $extra AS\n";
657 | $sql .= "BEGIN\n$body\nEND $procedureName;";
658 | return $sql;
659 | }
660 |
661 | public function db($dbname): string
662 | {
663 | return 'ALTER SESSION SET CURRENT_SCHEMA = "' . $dbname . '"';
664 | }
665 | }
666 |
--------------------------------------------------------------------------------
/lib/ext/PdoOne_Pgsql.php:
--------------------------------------------------------------------------------
1 | false];
36 |
37 | /**
38 | * PdoOne_Mysql constructor.
39 | *
40 | * @param PdoOne $parent
41 | */
42 | public function __construct(PdoOne $parent)
43 | {
44 | $this->parent = $parent;
45 | }
46 |
47 | public function construct($charset, $config): string
48 | {
49 | $this->config = array_merge($this->config, $config);
50 | $this->parent->database_delimiter0 = '"';
51 | $this->parent->database_delimiter1 = '"';
52 | $this->parent->database_identityName = 'IDENTITY';
53 | // you should check the correct value at select * from nls_session_parameterswhere parameter = 'NLS_DATE_FORMAT';
54 | PdoOne::$dateFormat = 'Y-m-d';
55 | PdoOne::$dateTimeFormat = 'Y-m-d H:i:s';
56 | PdoOne::$dateTimeMicroFormat = 'Y-m-d H:i:s.u';
57 | PdoOne::$isoDateInput = 'Y-m-d';
58 | PdoOne::$isoDateInputTime = 'Y-m-d H:i:s';
59 | PdoOne::$isoDateInputTimeMs = 'Y-m-d H:i:s.u';
60 | $this->parent->isOpen = false;
61 | return '';
62 | }
63 |
64 | public function connect($cs, $alterSession = true): void
65 | {
66 | // pgsql:host=localhost;port=5432;dbname=dvdrental;
67 | $cstring = "pgsql:host={$this->parent->server};options='--client_encoding=UTF8';port=5432;user={$this->parent->user};password={$this->parent->pwd};";
68 | $this->parent->conn1 = new PDO($cstring);
69 | $this->parent->user = '';
70 | $this->parent->pwd = '';
71 | $this->parent->conn1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
72 | $this->parent->conn1->setAttribute(PDO::ATTR_AUTOCOMMIT, true);
73 | $this->parent->conn1->setAttribute(PDO::ATTR_PERSISTENT, true);
74 | //$this->parent->conn1->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // otherwise return "0.1" as "0,1"
75 | //$this->parent->conn1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
76 | if ($alterSession) {
77 | /* $this->parent->conn1->exec("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'
78 | NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'
79 | NLS_TIMESTAMP_TZ_FORMAT='YYYY-MM-DD HH24:MI:SS'
80 | NLS_NUMERIC_CHARACTERS = '.,'");*/
81 | }
82 | $this->parent->isOpen = true; // we mark for open only to set the current schema
83 | $this->parent->db($this->parent->db);
84 | $this->parent->isOpen = false; // it will open in PdoOne (if no error).
85 | }
86 |
87 | public function truncate(string $tableName, string $extra, bool $force)
88 | {
89 | if (!$force) {
90 | $sql = 'truncate table ' . $this->parent->addDelimiter($tableName) . " $extra";
91 | return $this->parent->runRawQuery($sql);
92 | }
93 | $sql = "DELETE FROM " . $this->parent->addDelimiter($tableName) . " $extra";
94 | return $this->parent->runRawQuery($sql);
95 | }
96 |
97 | /**
98 | * @param string $tableName
99 | * @param int $newValue
100 | * @param string $column
101 | * @return array|bool|PDOStatement|null
102 | * @throws Exception
103 | */
104 | public function resetIdentity(string $tableName, int $newValue = 0, string $column = '')
105 | {
106 | $sql = "ALTER SEQUENCE {$tableName}_{$column}_seq RESTART WITH $newValue";
107 | return $this->parent->runRawQuery($sql);
108 | }
109 |
110 | /**
111 | * @param string $table
112 | * @param false $onlyDescription
113 | *
114 | * @return array|bool|mixed|PDOStatement|null
115 | * @throws Exception
116 | */
117 | public function getDefTableExtended(string $table, bool $onlyDescription = false)
118 | {
119 | $query = "SELECT t.table_name as table_name
120 | ,'' as engine
121 | ,t.table_catalog schema
122 | ,'' collation
123 | ,obj_description(pgc.oid, 'pg_class') description
124 | FROM information_schema.tables t
125 | INNER JOIN pg_catalog.pg_class pgc
126 | ON t.table_name = pgc.relname
127 | WHERE t.table_type='BASE TABLE'
128 | AND t.table_name=?
129 | and t.table_schema=?";
130 |
131 | $result = $this->parent->runRawQuery($query, [$table, $this->parent->db]);
132 | $result= array_change_key_case($result[0],0); // CASE_LOWER
133 | if ($onlyDescription) {
134 | return $result['description'];
135 | }
136 | return $result;
137 | }
138 |
139 | /**
140 | * todo pending
141 | * @param string $table
142 | * @return array
143 | * @throws Exception
144 | */
145 | public function getDefTable(string $table): array
146 | {
147 | /** @var array $result =array(["name"=>'',"is_identity"=>0,"increment_value"=>0,"seed_value"=>0]) */
148 | // throw new RuntimeException("no yet implemented");
149 | $raw = $this->parent->runRawQuery('select TO_CHAR(DBMS_METADATA.GET_DDL(\'TABLE\',?)) COL from dual', [$table]);
150 | if (!isset($raw[0]['COL'])) {
151 | return [];
152 | }
153 | $r = $raw[0]['COL'];
154 | $p0 = strpos($r, '(') + 1;
155 | $p1a = strpos($r, ' TABLESPACE ', $p0);
156 | $p1b = strpos($r, ' CONSTRAINT ', $p0);
157 | $p1c = strpos($r, ' USING ', $p0);
158 | $p1 = min($p1a, $p1b, $p1c);
159 | $rcut = trim(substr($r, $p0, $p1 - $p0), " \t\n\r\0\x0B,");
160 | $cols = explode(", \n", $rcut);
161 | $result = [];
162 | foreach ($cols as $v) {
163 | $key = explode(' ', $v . ' ', 2); // the last space avoid to return $key as an array with a single value.
164 | $result[PdoOne::removeDoubleQuotes($key[0])] = trim($key[1]);
165 | }
166 | return $result;
167 | }
168 |
169 | /**
170 | * todo pending
171 | * It gets a column from INFORMATION_SCHEMA.COLUMNS and returns a type of the form type,type(size)
172 | * or type(size,size)
173 | *
174 | * @param array $col An associative array with the data of the column
175 | *
176 | * @return string
177 | * @noinspection PhpUnused
178 | */
179 | protected static function pgsql_getType($col): string
180 | {
181 | throw new RuntimeException("no yet implemented");
182 | /** @var array $exclusion type of columns that don't use size */
183 | $exclusion = ['int', 'long', 'tinyint', 'year', 'bigint', 'bit', 'smallint', 'float', 'money'];
184 | if (in_array($col['DATA_TYPE'], $exclusion, true) !== false) {
185 | return $col['DATA_TYPE'];
186 | }
187 | if ($col['NUMERIC_SCALE']) {
188 | $result = "{$col['DATA_TYPE']}({$col['NUMERIC_PRECISION']},{$col['NUMERIC_SCALE']})";
189 | } elseif ($col['NUMERIC_PRECISION'] || $col['CHARACTER_MAXIMUM_LENGTH']) {
190 | $result = "{$col['DATA_TYPE']}(" . ($col['CHARACTER_MAXIMUM_LENGTH'] + $col['NUMERIC_PRECISION']) . ')';
191 | } else {
192 | $result = $col['DATA_TYPE'];
193 | }
194 | return $result;
195 | }
196 |
197 | /**
198 | * todo: pending
199 | * @param string $table
200 | * @param bool $returnSimple
201 | * @param string|null $filter
202 | * @return array
203 | * @throws Exception
204 | */
205 | public function getDefTableKeys(string $table, bool $returnSimple, ?string $filter = null): array
206 | {
207 | $columns = [];
208 | /** @var array $result =array(["IndexName"=>'',"ColumnName"=>'',"is_unique"=>0,"is_primary_key"=>0,"TYPE"=>0]) */
209 | $pks = $this->getPK($table);
210 | $result =
211 | $this->parent->select('SELECT ALL_indexes.INDEX_NAME "IndexName",all_ind_columns.COLUMN_NAME "ColumnName",
212 | (CASE WHEN UNIQUENESS = \'UNIQUE\' THEN 1 ELSE 0 END) "is_unique",0 "is_primary_key",0 "TYPE"')
213 | ->from('ALL_indexes')
214 | ->innerjoin('all_ind_columns on ALL_indexes.index_name=all_ind_columns.index_name ')
215 | ->where("ALL_indexes.table_name='$table' and ALL_indexes.table_owner='{$this->parent->db}'")
216 | ->order('"IndexName"')->toList();
217 | foreach ($result as $k => $item) {
218 | if (in_array($item['ColumnName'], $pks, true)) {
219 | $type = 'PRIMARY KEY';
220 | $result[$k]['is_primary_key'] = 1;
221 | } elseif ($item['is_unique']) {
222 | $type = 'UNIQUE KEY';
223 | } else {
224 | $type = 'KEY';
225 | }
226 | if ($filter === null || $filter === $type) {
227 | if ($returnSimple) {
228 | $columns[$item['ColumnName']] = $type;
229 | } else {
230 | $columns[$item['ColumnName']] = PdoOne::newColFK($type, '', '');
231 | }
232 | }
233 | }
234 | return $columns; //$this->parent->filterKey($filter, $columns, $returnSimple);
235 | }
236 |
237 | /**
238 | * todo: pending
239 | * @param string $table
240 | * @param bool $returnSimple
241 | * @param string|null $filter
242 | * @param bool $assocArray
243 | * @return array
244 | * @throws Exception
245 | * todo: missing checking
246 | */
247 | public function getDefTableFK(string $table, bool $returnSimple, ?string $filter = null, bool $assocArray = false): array
248 | {
249 | $columns = [];
250 | /** @var array $fkArr =array(["foreign_key_name"=>'',"referencing_table_name"=>'',"COLUMN_NAME"=>''
251 | * ,"referenced_table_name"=>'',"referenced_column_name"=>'',"referenced_schema_name"=>''
252 | * ,"update_referential_action_desc"=>'',"delete_referential_action_desc"=>''])
253 | */
254 | $fkArr = $this->parent->select('SELECT
255 | a.constraint_name "foreign_key_name",
256 | a.table_name "referencing_table_name",
257 | a.column_name "COLUMN_NAME",
258 | c_pk.table_name "referenced_table_name",
259 | b.column_name "referenced_column_name",
260 | c_pk.OWNER "referenced_schema_name",
261 | \'\' "update_referential_action_desc",
262 | c.DELETE_RULE "delete_referential_action_desc"
263 | FROM
264 | user_cons_columns a
265 | JOIN all_constraints c ON
266 | a.owner = c.owner
267 | AND a.constraint_name = c.constraint_name
268 | JOIN all_constraints c_pk ON
269 | c.r_owner = c_pk.owner
270 | AND c.r_constraint_name = c_pk.constraint_name
271 | LEFT JOIN USER_CONS_COLUMNS b ON
272 | b.OWNER = C_PK.owner
273 | AND b.CONSTRAINT_NAME = c_pk.CONSTRAINT_NAME')
274 | ->where("c.constraint_type = 'R' AND a.table_name=?", [$table])
275 | ->order('a.column_name')->toList();
276 | foreach ($fkArr as $item) {
277 | $extra = ($item['update_referential_action_desc'] !== 'NO_ACTION') ? ' ON UPDATE ' .
278 | str_replace('_', ' ', $item['update_referential_action_desc']) : '';
279 | $extra .= ($item['delete_referential_action_desc'] !== 'NO_ACTION') ? ' ON DELETE ' .
280 | str_replace('_', ' ', $item['delete_referential_action_desc']) : '';
281 | //FOREIGN KEY REFERENCES TABLEREF(COLREF)
282 | if ($returnSimple) {
283 | $columns[$item['COLUMN_NAME']] =
284 | 'FOREIGN KEY REFERENCES ' . $this->parent->addQuote($item['referenced_table_name'])
285 | . '(' . $this->parent->addQuote($item['referenced_column_name']) . ')' . $extra;
286 | } else {
287 | $columns[$item['COLUMN_NAME']] = PdoOne::newColFK('FOREIGN KEY'
288 | , $item['referenced_column_name']
289 | , $item['referenced_table_name']
290 | , $extra
291 | , $item['foreign_key_name']); // fk_name
292 | $columns[PdoOne::addPrefix($item['COLUMN_NAME'])] = PdoOne::newColFK(
293 | 'MANYTOONE'
294 | , $item['referenced_column_name']
295 | , $item['referenced_table_name']
296 | , $extra
297 | , $item['foreign_key_name']); // fk_name
298 | }
299 | }
300 | if ($assocArray) {
301 | return $columns;
302 | }
303 | return $this->parent->filterKey($filter, $columns, $returnSimple);
304 | }
305 |
306 | /**
307 | * todo: pending
308 | * @param $row
309 | * @param bool $default
310 | * @return string
311 | */
312 | function typeDict($row, bool $default = true): string
313 | {
314 | $type = strtolower(@$row['pgsql:decl_type']);
315 | switch ($type) {
316 | case 'varchar':
317 | case 'varchar2':
318 | case 'nvarchar':
319 | case 'nvarchar2':
320 | case 'text':
321 | case 'ntext':
322 | case 'char':
323 | case 'nchar':
324 | case 'binary':
325 | case 'varbinary':
326 | case 'timestamp':
327 | case 'time':
328 | case 'date':
329 | case 'smalldatetime':
330 | case 'datetime2':
331 | case 'datetimeoffset':
332 | case 'datetime':
333 | case 'image':
334 | return ($default) ? "''" : 'string';
335 | case 'long':
336 | case 'tinyint':
337 | case 'number':
338 | case 'int':
339 | case 'sql_variant':
340 | case 'int identity':
341 | case 'year':
342 | case 'bigint':
343 | case 'numeric':
344 | case 'bit':
345 | case 'smallint':
346 | return ($default) ? '0' : 'int';
347 | case 'decimal':
348 | case 'smallmoney':
349 | case 'money':
350 | case 'double':
351 | case 'real':
352 | case 'float':
353 | return ($default) ? '0.0' : 'float';
354 | default:
355 | return '???pgsql:' . $type;
356 | }
357 | }
358 |
359 | public function objectExist(string $type = 'table'): ?string
360 | {
361 | switch ($type) {
362 | case 'table':
363 | $query = 'SELECT * FROM pg_catalog.pg_tables where tablename=? and tableowner=?';
364 | break;
365 | case 'procedure':
366 | $query = "SELECT * FROM ALL_OBJECTS WHERE routine_type='PROCEDURE' and routine_name=? and routine_schema=?";
367 | break;
368 | case 'function':
369 | $query = "SELECT * FROM ALL_OBJECTS WHERE routine_type='FUNCTION' and routine_name=? and routine_schema=?";
370 | break;
371 | default:
372 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
373 | return null;
374 | }
375 | return $query;
376 | }
377 |
378 | /**
379 | * @param string $type
380 | * @param bool $onlyName
381 | * @return array|string|string[]|null
382 | * @throws JsonException
383 | */
384 | public function objectList(string $type = 'table', bool $onlyName = false)
385 | {
386 | switch ($type) {
387 | case 'table':
388 | $query = "select * from pg_catalog.pg_tables where schemaname=?";
389 | if ($onlyName) {
390 | $query = str_replace('*', 'tablename name', $query);
391 | }
392 | break;
393 | case 'procedure':
394 | $query = "SELECT routine_name FROM information_schema.routines WHERE routine_type='PROCEDURE' and routine_schema=?;";
395 | if ($onlyName) {
396 | $query = str_replace('*', 'routine_name name', $query);
397 | }
398 | break;
399 | case 'function':
400 | $query = "SELECT routine_name FROM information_schema.routines WHERE routine_type='FUNCTION' and routine_schema=?;";
401 | if ($onlyName) {
402 | $query = str_replace('*', 'routine_name name', $query);
403 | }
404 | break;
405 | default:
406 | $this->parent->throwError("objectExist: type [$type] not defined for {$this->parent->databaseType}", '');
407 | return null;
408 | }
409 | return $query;
410 | }
411 |
412 | /**
413 | * @inheritDoc
414 | */
415 | public function columnTable($tableName):string
416 | {
417 | return "SELECT
418 | col.column_name as colname,
419 | col.data_type as coltype,
420 | COALESCE(col.numeric_precision,0)+COALESCE(character_maximum_length,0) as colsize,
421 | col.numeric_precision as colpres,
422 | col.numeric_scale as colscale,
423 | (case when (ts.constraint_type is null) then 0 else 1 end) as iskey,
424 | col.is_identity,
425 | col.is_nullable
426 | FROM information_schema.columns col
427 | left join information_schema.key_column_usage kcu
428 | on col.table_name=kcu.table_name
429 | and col.table_schema=kcu.table_schema
430 | and col.column_name =kcu.column_name
431 | left join information_schema.table_constraints ts
432 | on kcu.table_schema = ts.table_schema
433 | and kcu.table_name = ts.table_name
434 | and kcu.table_catalog = ts.table_catalog
435 | and ts.constraint_type='PRIMARY KEY'
436 | WHERE col.table_schema = '{$this->parent->db}'
437 | AND col.table_name = '$tableName'";
438 | }
439 |
440 | /**
441 | * @param $tableName
442 | * @return string
443 | */
444 | public function foreignKeyTable($tableName): string
445 | {
446 | return "SELECT
447 | kcu.column_name as collocal,
448 | ccu.table_name AS tablerem,
449 | ccu.column_name AS colrem ,
450 | tc.constraint_name as fk_name
451 | FROM information_schema.table_constraints AS tc
452 | JOIN information_schema.key_column_usage AS kcu
453 | ON tc.constraint_name = kcu.constraint_name
454 | AND tc.table_schema = kcu.table_schema
455 | JOIN information_schema.constraint_column_usage AS ccu
456 | ON ccu.constraint_name = tc.constraint_name
457 | WHERE tc.constraint_type = 'FOREIGN KEY'
458 | AND tc.table_schema='{$this->parent->db}'
459 | AND tc.table_name='$tableName'";
460 | }
461 |
462 | public function createSequence(?string $tableSequence = null, string $method = 'snowflake'): array
463 | {
464 | return ["CREATE SEQUENCE $tableSequence
465 | INCREMENT BY 1
466 | START WITH 1"];
467 | }
468 |
469 | public function getSequence($sequenceName): string
470 | {
471 | $sequenceName = ($sequenceName == '') ? $this->parent->tableSequence : $sequenceName;
472 | return "select \"$sequenceName\".nextval as \"id\" from dual";
473 | }
474 |
475 | public function translateExtra($universalExtra): string
476 | {
477 | /** @noinspection DegradedSwitchInspection */
478 | switch ($universalExtra) {
479 | case 'autonumeric':
480 | $sqlExtra = 'GENERATED BY DEFAULT AS IDENTITY';
481 | break;
482 | default:
483 | $sqlExtra = $universalExtra;
484 | }
485 | return $sqlExtra;
486 | }
487 |
488 | public function translateType($universalType, $len = null): string
489 | {
490 | switch ($universalType) {
491 | case 'int':
492 | $sqlType = "int";
493 | break;
494 | case 'long':
495 | $sqlType = "long";
496 | break;
497 | case 'decimal':
498 | $sqlType = "decimal($len) ";
499 | break;
500 | case 'bool':
501 | $sqlType = "char(1)";
502 | break;
503 | case 'date':
504 | case 'datetime':
505 | $sqlType = "date";
506 | break;
507 | case 'timestamp':
508 | $sqlType = "timestamp";
509 | break;
510 | case 'string':
511 | default:
512 | $sqlType = "varchar2($len) ";
513 | break;
514 | }
515 | return $sqlType;
516 | }
517 |
518 | public function createTable(string $tableName, array $definition, $primaryKey = null, string $extra = '', string $extraOutside = ''): string
519 | {
520 | $sql = "CREATE TABLE \"$tableName\" (";
521 | foreach ($definition as $key => $type) {
522 | $sql .= "\"$key\" $type,";
523 | }
524 | $sql = rtrim($sql, ',');
525 | $sql .= "$extra ); ";
526 | if ($primaryKey !== null) {
527 | if (!is_array($primaryKey)) {
528 | $pks = is_array($primaryKey) ? implode(',', $primaryKey) : $primaryKey;
529 | $sql .= "ALTER TABLE \"$tableName\" ADD (
530 | CONSTRAINT {$tableName}_PK PRIMARY KEY ($pks)
531 | ENABLE VALIDATE) $extraOutside;";
532 | } else {
533 | $hasPK = false;
534 | // ['field'=>'FOREIGN KEY REFERENCES TABLEREF(COLREF) ...]
535 | foreach ($primaryKey as $key => $value) {
536 | $p0 = stripos($value . ' ', 'KEY ');
537 | if ($p0 === false) {
538 | trigger_error('createTable: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
539 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
540 | break;
541 | }
542 | $type = strtoupper(trim(substr($value, 0, $p0)));
543 | $value = substr($value, $p0 + 4);
544 | switch ($type) {
545 | case 'PRIMARY':
546 | if (!$hasPK) {
547 | $sql .= "ALTER TABLE \"$tableName\" ADD ( CONSTRAINT PK_$tableName PRIMARY KEY(\"$key*pk*\") ENABLE VALIDATE);";
548 | $hasPK = true;
549 | } else {
550 | $sql = str_replace('*pk*', ",$key", $sql); // we add an extra primary key
551 | }
552 | break;
553 | case '':
554 | $sql .= "CREATE INDEX \"{$tableName}_{$key}_KEY\" ON $tableName (\"$key\") $value;";
555 | break;
556 | case 'UNIQUE':
557 | $sql .= "CREATE UNIQUE INDEX \"{$tableName}_{$key}_UK\" ON $tableName (\"$key\") $value;";
558 | break;
559 | case 'FOREIGN':
560 | $sql .= "ALTER TABLE \"$tableName\" ADD CONSTRAINT {$tableName}_{$key}_FK FOREIGN KEY (\"$key\") $value;";
561 | break;
562 | default:
563 | trigger_error("createTable: [$type KEY] not defined");
564 | break;
565 | }
566 | }
567 | $sql = str_replace('*pk*', '', $sql);
568 | }
569 | }
570 | return $sql;
571 | }
572 | public function addColumn(string $tableName,array $definition):string {
573 | $sql = "ALTER TABLE \"$tableName\" ADD (";
574 | foreach ($definition as $key => $type) {
575 | $sql .= "\"$key\" $type,";
576 | }
577 | return rtrim($sql,',').')';
578 | }
579 | public function deleteColumn(string $tableName, $columnName): string {
580 | if(!is_array($columnName)) {
581 | $columnName=[$columnName];
582 | }
583 | $sql = "ALTER TABLE \"$tableName\" DROP(";
584 | foreach($columnName as $c) {
585 | $sql .= "$c,";
586 | }
587 | return rtrim($sql,';').')';
588 | }
589 |
590 | public function createFK(string $tableName, array $foreignKeys): ?string
591 | {
592 | $sql = '';
593 | foreach ($foreignKeys as $key => $value) {
594 | $p0 = stripos($value . ' ', 'KEY ');
595 | if ($p0 === false) {
596 | trigger_error('createFK: Key with a wrong syntax. Example: "PRIMARY KEY.." ,
597 | "KEY...", "UNIQUE KEY..." "FOREIGN KEY.." ');
598 | return null;
599 | }
600 | $type = strtoupper(trim(substr($value, 0, $p0)));
601 | $value = substr($value, $p0 + 4);
602 | if ($type === 'FOREIGN') {
603 | $sql .= "ALTER TABLE \"$tableName\" ADD CONSTRAINT {$tableName}_fk_$key FOREIGN KEY (\"$key\") $value;";
604 | }
605 | }
606 | return $sql;
607 | }
608 |
609 | public function createIndex(string $tableName, array $indexesAndDef): string
610 | {
611 | throw new RuntimeException('not tested');
612 | $sql = '';
613 | foreach ($indexesAndDef as $key => $typeIndex) {
614 | $sql .= "ALTER TABLE `$tableName` ADD $typeIndex `idx_{$tableName}_$key` (`$key`) ;";
615 | }
616 | return $sql;
617 | }
618 |
619 | /**
620 | * For 12c and higher.
621 | *
622 | * @param int|null $first
623 | * @param int|null $second
624 | * @return string
625 | */
626 | public function limit(?int $first, ?int $second): string
627 | {
628 | if ($second === null) {
629 | return " OFFSET 0 ROWS FETCH NEXT $first ROWS ONLY";
630 | }
631 | return " OFFSET $first ROWS FETCH NEXT $second ROWS ONLY";
632 | }
633 |
634 | /**
635 | * todo: not tested
636 | * @return string
637 | */
638 | public function now(): string
639 | {
640 | return "select TO_CHAR(SYSDATE, 'YYYY-DD-MM HH24:MI:SS') as NOW from dual";
641 | }
642 |
643 | /**
644 | *
645 | * @param string $tableKV
646 | * @param bool $memoryKV You must set this value in the tablespace and not here.
647 | * @return string
648 | */
649 | public function createTableKV($tableKV, $memoryKV = false): string
650 | {
651 | return $this->createTable($tableKV
652 | , ['KEYT' => 'VARCHAR2(256)', 'VALUE' => 'CLOB', 'TIMESTAMP' => 'BIGINT']
653 | , 'KEYT');
654 | }
655 |
656 | public function getPK($query, $pk = null)
657 | {
658 | try {
659 | $pkResult = [];
660 | if ($this->parent->isQuery($query)) {
661 | if (!$pk) {
662 | return 'PGSQL: unable to find pk via query. Use the name of the table';
663 | }
664 | } else {
665 | $q = "select kcu.column_name as \"RESULT\"
666 | from information_schema.table_constraints tco
667 | join information_schema.key_column_usage kcu
668 | on kcu.constraint_name = tco.constraint_name
669 | and kcu.constraint_schema = tco.constraint_schema
670 | and kcu.constraint_name = tco.constraint_name
671 | where tco.constraint_type = 'PRIMARY KEY'
672 | and kcu.table_name=?
673 | and kcu.table_schema=?";
674 | $r = $this->parent->runRawQuery($q, [$query, $this->parent->db]);
675 | if (count($r) >= 1) {
676 | foreach ($r as $item) {
677 | $pkResult[] = $item['RESULT'];
678 | }
679 | } else {
680 | $pkResult[] = '??nopk??';
681 | }
682 | }
683 | $pkAsArray = (is_array($pk)) ? $pk : array($pk);
684 | return count($pkResult) === 0 ? $pkAsArray : $pkResult;
685 | } catch (Exception $ex) {
686 | return false;
687 | }
688 | }
689 | public function callProcedure(string $procName, array &$arguments = [], array $outputColumns = [])
690 | {
691 | // TODO: Implement callProcedure() method.
692 | throw new RuntimeException('not defined');
693 | return null;
694 | }
695 |
696 | public function createProcedure(string $procedureName, $arguments = [], string $body = '', string $extra = ''): string
697 | {
698 | if (is_array($arguments)) {
699 | $sqlArgs = '';
700 | foreach ($arguments as $k => $v) {
701 | if (is_array($v)) {
702 | if (count($v) > 2) {
703 | $sqlArgs .= "$v[1] $v[0] $v[2],";
704 | } else {
705 | $sqlArgs .= "$v[1] in $v[2],";
706 | }
707 | } else {
708 | $sqlArgs .= "$k in $v,";
709 | }
710 | }
711 | $sqlArgs = trim($sqlArgs, ',');
712 | } else {
713 | $sqlArgs = $arguments;
714 | }
715 | $sql = "CREATE OR REPLACE PROCEDURE \"$procedureName\" ($sqlArgs) $extra AS\n";
716 | $sql .= "BEGIN\n$body\nEND $procedureName;";
717 | return $sql;
718 | }
719 |
720 | public function db($dbname): string
721 | {
722 | return "SET search_path TO $dbname";
723 | }
724 | }
725 |
--------------------------------------------------------------------------------