Skip to content

DigitalCyberSoft/php-mysqlipg

Repository files navigation

php-mysqlipg

A PHP C extension that provides the mysqli API backed by PostgreSQL via libpq. Drop-in replacement that lets existing PHP applications written for MySQL/mysqli run against PostgreSQL with zero code changes.

PHP userland (mysqli_* calls) → mysqlipg C extension → SQL translator (C) → libpq → PostgreSQL

Requirements

  • PHP 8.0+ with development headers (php-devel / php-dev)
  • PostgreSQL client library (libpq-devel / libpq-dev)
  • C compiler (gcc/clang)

Installation

# Install dependencies (Fedora/RHEL)
dnf install php-devel libpq-devel

# Install dependencies (Debian/Ubuntu)
apt install php-dev libpq-dev

# Build the extension
cd php-mysqlipg
phpize
./configure --with-mysqlipg
make
make test    # runs 34 tests

# Install system-wide (optional)
make install

# Load the extension
echo "extension=mysqlipg.so" >> $(php-config --ini-dir)/mysqlipg.ini

# Or load per-invocation
php -d extension=mysqlipg.so your_script.php

Verify

php -d extension=modules/mysqlipg.so -m | grep mysqlipg
# → mysqlipg

php -d extension=modules/mysqlipg.so -r "phpinfo();" | grep mysqlipg
# → mysqlipg support => enabled

Quick Start

Procedural API

// Connect to PostgreSQL using the mysqli-style API
$conn = mysqlipg_connect('localhost', 'postgres', 'password', 'mydb', 5432);

// Queries are automatically translated from MySQL to PostgreSQL
$result = mysqlipg_query($conn, "SHOW TABLES");
while ($row = mysqlipg_fetch_assoc($result)) {
    print_r($row);
}

// MySQL functions are translated transparently
$result = mysqlipg_query($conn, "SELECT IFNULL(name, 'unknown'), RAND() FROM users LIMIT 10,5");
// Becomes: SELECT COALESCE(name, 'unknown'), RANDOM() FROM users LIMIT 5 OFFSET 10

// Backtick-quoted identifiers work
$result = mysqlipg_query($conn, "SELECT `user`.`name` FROM `user` WHERE `id` = 1");

// Prepared statements
$stmt = mysqlipg_prepare($conn, "SELECT * FROM users WHERE age > ? AND city = ?");
mysqlipg_stmt_bind_param($stmt, "is", $age, $city);
mysqlipg_stmt_execute($stmt);
$result = mysqlipg_stmt_get_result($stmt);
while ($row = mysqlipg_fetch_assoc($result)) {
    print_r($row);
}
mysqlipg_stmt_close($stmt);

mysqlipg_close($conn);

OOP API

$conn = new MysqliPg('localhost', 'postgres', 'password', 'mydb', 5432);

$result = $conn->query("SELECT DATABASE() AS db");
$row = $result->fetch_assoc();
echo $row['db']; // current PostgreSQL database name

// Transactions
$conn->begin_transaction();
$conn->query("INSERT INTO orders (product, qty) VALUES ('Widget', 5)");
$conn->query("UPDATE inventory SET stock = stock - 5 WHERE product = 'Widget'");
$conn->commit();

// Prepared statements
$stmt = $conn->prepare("INSERT INTO logs (msg, level) VALUES (?, ?)");
$stmt->bind_param("si", $message, $level);
$stmt->execute();
echo $conn->insert_id;
$stmt->close();

$conn->close();

SQL Translation

The extension automatically translates MySQL SQL to PostgreSQL. 80+ patterns are handled:

Identifier Quoting

MySQL PostgreSQL
`table`.`column` "table"."column"

Functions

MySQL PostgreSQL
RAND() RANDOM()
IFNULL(a, b) COALESCE(a, b)
DATABASE() current_database()
CURDATE() CURRENT_DATE
SYSDATE() CLOCK_TIMESTAMP()
LAST_INSERT_ID() lastval()
IF(cond, a, b) CASE WHEN cond THEN a ELSE b END
ISNULL(x) (x IS NULL)
DATEDIFF(a, b) (a::date - b::date)
DATE_ADD(d, INTERVAL) (d + INTERVAL)
DATE_FORMAT(d, '%Y-%m-%d') TO_CHAR(d, 'YYYY-MM-DD')
STR_TO_DATE(s, fmt) TO_TIMESTAMP(s, pg_fmt)
YEAR(d) / MONTH(d) / DAY(d) EXTRACT(YEAR FROM d)
LAST_DAY(d) (DATE_TRUNC('month',d)+INTERVAL '1 month'-INTERVAL '1 day')::date
GROUP_CONCAT(col SEPARATOR ',') STRING_AGG(col::text, ',')
FIELD(v, a, b, c) ARRAY_POSITION(ARRAY[a,b,c], v)
ELT(n, a, b, c) (ARRAY[a,b,c])[n]
FIND_IN_SET(v, 'a,b,c') v = ANY(string_to_array('a,b,c',','))
STRCMP(a, b) CASE WHEN a<b THEN -1 WHEN a>b THEN 1 ELSE 0 END
LOG(x) LN(x)
LOG2(x) / LOG10(x) LOG(2,x) / LOG(10,x)
TRUNCATE(n, d) TRUNC(n, d)
FROM_UNIXTIME(n) TO_TIMESTAMP(n)
UNIX_TIMESTAMP() EXTRACT(EPOCH FROM NOW())::bigint
CONVERT(x, UNSIGNED) CAST(x AS BIGINT)
VERSION() version()
DATE(col) / TIME(col) (col)::date / (col)::time

SHOW Commands

MySQL PostgreSQL
SHOW DATABASES pg_database query
SHOW TABLES information_schema.tables query
SHOW FULL TABLES includes Table_type column
SHOW COLUMNS FROM t information_schema.columns query
SHOW INDEX FROM t pg_class/pg_index query
DESC table information_schema.columns with type mapping
SHOW TABLE STATUS information_schema.tables query
SHOW VARIABLES pg_settings query
SHOW PROCESSLIST pg_stat_activity query
SHOW GRANTS role_table_grants query
SHOW WARNINGS empty result
SHOW ENGINES returns PostgreSQL

DML Translation

MySQL PostgreSQL
INSERT ... SET col=val INSERT ... (col) VALUES (val)
INSERT IGNORE INTO ... INSERT ... ON CONFLICT DO NOTHING
INSERT LOW_PRIORITY ... modifier stripped
TRUNCATE TABLE t TRUNCATE TABLE t RESTART IDENTITY
LIMIT 10, 5 LIMIT 5 OFFSET 10
'0000-00-00 00:00:00' NULL

DDL Translation

MySQL PostgreSQL
ALTER TABLE t MODIFY col TYPE ALTER TABLE t ALTER COLUMN col TYPE
ALTER TABLE t CHANGE old new TYPE RENAME COLUMN + ALTER TYPE
ALTER TABLE t ADD INDEX idx (col) CREATE INDEX idx ON t (col)
ALTER TABLE t DROP INDEX idx DROP INDEX idx
RENAME TABLE old TO new ALTER TABLE old RENAME TO new
CREATE DATABASE [IF NOT EXISTS] db CREATE DATABASE db

Operators & Syntax

MySQL PostgreSQL
<=> IS NOT DISTINCT FROM
REGEXP / RLIKE ~*
NOT REGEXP !~*
FROM DUAL stripped
STRAIGHT_JOIN JOIN
SQL_CALC_FOUND_ROWS stripped
SQL_NO_CACHE stripped
LOCK IN SHARE MODE FOR SHARE
USE INDEX(...) stripped
CAST(x AS UNSIGNED) CAST(x AS BIGINT)
WITH ROLLUP stripped

No-Op Translations

These MySQL statements are accepted and silently handled:

  • SET NAMES utf8mb4SET client_encoding = 'UTF8'
  • SET FOREIGN_KEY_CHECKS = 0/1SET session_replication_role
  • SET sql_mode, SET CHARACTER_SET_*, SET @@session.* → no-op
  • LOCK TABLES / UNLOCK TABLES → no-op
  • ALTER TABLE ... DISABLE/ENABLE KEYS → no-op
  • FLUSH PRIVILEGES → no-op
  • DO exprSELECT expr
  • KILL pidSELECT pg_terminate_backend(pid)

API Reference

Connection Functions

Procedural OOP Description
mysqlipg_connect(host, user, pass, db, port) new MysqliPg(...) Connect to PostgreSQL
mysqlipg_close($conn) $conn->close() Close connection
mysqlipg_query($conn, $sql) $conn->query($sql) Execute query
mysqlipg_real_escape_string($conn, $str) $conn->real_escape_string($str) Escape string
mysqlipg_select_db($conn, $db) $conn->select_db($db) Change database
mysqlipg_multi_query($conn, $sql) $conn->multi_query($sql) Execute multiple queries
mysqlipg_more_results($conn) $conn->more_results() Check for more results
mysqlipg_next_result($conn) $conn->next_result() Move to next result
mysqlipg_store_result($conn) $conn->store_result() Get result from multi-query
mysqlipg_ping($conn) $conn->ping() Check connection
mysqlipg_set_charset($conn, $cs) $conn->set_charset($cs) Set character set
mysqlipg_character_set_name($conn) $conn->character_set_name() Get character set
mysqlipg_stat($conn) $conn->stat() Server status
mysqlipg_thread_id($conn) Backend PID

Transaction Functions

Procedural OOP Description
mysqlipg_autocommit($conn, $bool) $conn->autocommit($bool) Toggle autocommit
mysqlipg_begin_transaction($conn) $conn->begin_transaction() Start transaction
mysqlipg_commit($conn) $conn->commit() Commit
mysqlipg_rollback($conn) $conn->rollback() Rollback
mysqlipg_savepoint($conn, $name) $conn->savepoint($name) Create savepoint
mysqlipg_release_savepoint($conn, $name) Release savepoint

State Functions

Procedural OOP Property Description
mysqlipg_affected_rows($conn) $conn->affected_rows Affected row count
mysqlipg_insert_id($conn) $conn->insert_id Last insert ID
mysqlipg_error($conn) $conn->error Last error message
mysqlipg_errno($conn) $conn->errno Last error number
mysqlipg_sqlstate($conn) $conn->sqlstate SQLSTATE code
mysqlipg_field_count($conn) $conn->field_count Field count
$conn->warning_count Warning count
$conn->host_info Host info string
$conn->server_info Server version string
$conn->server_version Server version int

Result Functions

Procedural OOP Description
mysqlipg_fetch_assoc($result) $result->fetch_assoc() Fetch as assoc array
mysqlipg_fetch_array($result, $mode) $result->fetch_array($mode) Fetch with mode
mysqlipg_fetch_row($result) $result->fetch_row() Fetch as numeric array
mysqlipg_fetch_all($result, $mode) $result->fetch_all($mode) Fetch all rows
mysqlipg_fetch_column($result, $col) $result->fetch_column($col) Fetch single column
mysqlipg_num_rows($result) $result->num_rows Row count
mysqlipg_num_fields($result) $result->num_fields Field count
mysqlipg_fetch_field($result) $result->fetch_field() Field metadata
mysqlipg_fetch_fields($result) $result->fetch_fields() All field metadata
mysqlipg_data_seek($result, $offset) $result->data_seek($offset) Seek to row
mysqlipg_free_result($result) $result->free() Free result

Prepared Statement Functions

Procedural OOP Description
mysqlipg_prepare($conn, $query) $conn->prepare($query) Prepare statement
mysqlipg_stmt_bind_param($stmt, $types, ...) $stmt->bind_param($types, ...) Bind parameters
mysqlipg_stmt_execute($stmt) $stmt->execute() Execute statement
mysqlipg_stmt_get_result($stmt) $stmt->get_result() Get result set
mysqlipg_stmt_close($stmt) $stmt->close() Close statement
mysqlipg_stmt_affected_rows($stmt) Affected rows
mysqlipg_stmt_num_rows($stmt) Row count
mysqlipg_stmt_error($stmt) Error message
mysqlipg_stmt_errno($stmt) Error number
mysqlipg_stmt_insert_id($stmt) Insert ID

Constants

  • MYSQLIPG_ASSOC, MYSQLIPG_NUM, MYSQLIPG_BOTH — fetch modes
  • MYSQLIPG_TYPE_* — field type constants (TINY, SHORT, LONG, FLOAT, DOUBLE, BLOB, VAR_STRING, DATE, TIME, DATETIME, etc.)
  • MYSQLIPG_*_FLAG — field flags (NOT_NULL, PRI_KEY, AUTO_INCREMENT, etc.)
  • MYSQLIPG_STORE_RESULT, MYSQLIPG_USE_RESULT
  • MYSQLIPG_REPORT_OFF, MYSQLIPG_REPORT_ERROR, MYSQLIPG_REPORT_STRICT, MYSQLIPG_REPORT_ALL

Testing

# Run all tests
make test

# Run specific test
make test TESTS=tests/mysqlipg_prepared_stmt.phpt

# Set custom PG connection for tests
export MYSQLIPG_TEST_HOST=localhost
export MYSQLIPG_TEST_USER=postgres
export MYSQLIPG_TEST_PASS=secret
export MYSQLIPG_TEST_DB=testdb
export MYSQLIPG_TEST_PORT=5432
make test

Architecture

┌──────────────────────────────────────────┐
│  PHP Application (uses mysqli_* calls)    │
├──────────────────────────────────────���───┤
│  mysqlipg C Extension                     │
│  ├─ mysqlipg.c          Module init       │
│  ├─ mysqlipg_connection.c  Connect/query  │
│  ├─ mysqlipg_result.c      Fetch/fields   │
│  ├─ mysqlipg_stmt.c        Prepared stmts │
│  ├─ mysqlipg_translator.c  SQL translator │
│  └─ mysqlipg_props.c       Property hdlrs │
├──────────────────────────────────────────┤
│  libpq (PostgreSQL client library)        │
├──────────────────────────────────────────┤
│  PostgreSQL Server                        │
└──────────────────────────────────────────┘

Related

  • mysqlpg — MySQL-compatible CLI for PostgreSQL (Python), the translation patterns were ported from this project

License

MIT

About

PHP C extension providing the mysqli API backed by PostgreSQL via libpq

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors