├── 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 | --------------------------------------------------------------------------------