1. Go to this page and download the library: Download darkspock/just-query library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
use JustQuery\Driver\Mysql\Connection;
// Reuse a PDO instance from your framework (CodeIgniter, Laravel, etc.)
// Use the matching Connection class for your driver.
$db = Connection::fromPdo($existingPdo);
use JustQuery\Cache\SchemaCache;
use JustQuery\Driver\Mysql\{Connection, Driver};
$driver = new Driver(
'mysql:host=127.0.0.1;dbname=myapp;port=3306',
'root',
'secret',
);
$schemaCache = new SchemaCache($psr16Cache);
$db = new Connection($driver, $schemaCache);
$db->open(); // Establish connection
$db->isActive(); // true if connected
$db->close(); // Close connection
$db->getDriverName(); // 'mysql' or 'pgsql'
$db->getLastInsertId(); // Last auto-increment ID
$db->getSchema(); // SchemaInterface
$db->getTableSchema('users'); // TableSchemaInterface for a specific table
$db->getQueryBuilder(); // QueryBuilderInterface
$db->getQuoter(); // QuoterInterface
$db->setTablePrefix('tbl_');
// Now {{%users}} resolves to tbl_users in queries
$db->getTablePrefix(); // 'tbl_'
// app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use JustQuery\Driver\Mysql\Connection;
use JustQuery\Schema\Provider\{SchemaProvider, SchemaMode};
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Connection::class, function ($app) {
$pdo = $app['db']->connection()->getPdo();
$db = Connection::fromPdo($pdo);
$db->setSchemaProvider(new SchemaProvider(
SchemaMode::JSON,
jsonPath: base_path('database/schema/'),
));
return $db;
});
}
}
use JustQuery\Driver\Mysql\Connection;
use JustQuery\Query\Query;
class UserController extends Controller
{
public function index(Connection $db)
{
return (new Query($db))
->from('users')
->where(['status' => 'active'])
->all();
}
}
// src/Factory/JustQueryFactory.php
namespace App\Factory;
use Doctrine\DBAL\Connection as DoctrineConnection;
use JustQuery\Driver\Mysql\Connection;
class JustQueryFactory
{
public function __construct(private DoctrineConnection $doctrine) {}
public function create(): Connection
{
return Connection::fromPdo(
$this->doctrine->getNativeConnection(),
);
}
}
use JustQuery\Driver\Mysql\Connection;
use JustQuery\Query\Query;
class UserController extends AbstractController
{
public function index(Connection $db): JsonResponse
{
$users = (new Query($db))
->from('users')
->where(['status' => 'active'])
->all();
return $this->json($users);
}
}
// Create a Query from the connection
$query = $db->createQuery();
// Shorthand: create a Query with SELECT columns
$query = $db->select(['id', 'name'])->from('users');
// Create a raw command
$command = $db->createCommand('SELECT * FROM users WHERE id = :id', [':id' => 1]);
// Normal IN: SELECT * FROM users WHERE id IN (:qp0, :qp1, ... :qp9999) — 10k bindings
// Raw IN: SELECT * FROM users WHERE id IN (1, 2, 3, ... 10000) — zero bindings
$userIds = [1, 2, 3, /* ... thousands of IDs */];
$query->from('users')
->whereIntegerInRaw('id', $userIds)
->all();
// NOT IN variant
$query->from('users')
->whereIntegerNotInRaw('id', $excludedIds)
->all();
$query->from('orders')
->select(['user_id', 'total' => 'SUM(amount)'])
->groupBy('user_id')
->addGroupBy('status') // add to existing GROUP BY
->having(['>', 'SUM(amount)', 1000])
->andHaving(['status' => 'completed'])
->orHaving(['>', 'COUNT(*)', 5]);
// Overwrite HAVING
$query->setHaving(['>', 'SUM(amount)', 500]);
// Filter HAVING (ignores null/empty values)
$query->filterHaving(['status' => $userInput]);
$query->andFilterHaving(['category' => $category]);
$query->orFilterHaving(['region' => $region]);
$query->orderBy(['created_at' => SORT_DESC]);
$query->addOrderBy(['name' => SORT_ASC]); // add to existing ORDER BY
$query->orderBy('created_at DESC, name ASC'); // string format
(new Query($db))->from('users')->count('*');
(new Query($db))->from('orders')->sum('total');
(new Query($db))->from('users')->average('age');
(new Query($db))->from('users')->min('age');
(new Query($db))->from('users')->max('age');
$query = (new Query($db))->from('users')->where(['status' => 'active']);
$rows = $query->all(); // All rows as array of arrays
$row = $query->one(); // First row or null
$exists = $query->exists(); // true if any rows match
$ids = $query->column(); // First column of all rows as flat array
$value = $query->scalar(); // Single value (first column, first row)
// Index results by the 'id' column
$users = (new Query($db))->from('users')->indexBy('id')->all();
// Result: [1 => ['id' => 1, 'name' => 'Alice'], 2 => ['id' => 2, 'name' => 'Bob']]
// Index by a closure
$users = (new Query($db))->from('users')
->indexBy(fn(array $row) => $row['email'])
->all();
// In FROM
$sub = (new Query($db))->select('user_id, SUM(total) as total')->from('orders')->groupBy('user_id');
$query->from(['totals' => $sub])->where(['>', 'totals.total', 1000]);
// In SELECT
$query->select(['name', 'order_count' => (new Query($db))->select('COUNT(*)')->from('orders')->where('orders.user_id = users.id')]);
$active = (new Query($db))->from('users')->where(['status' => 'active']);
$vip = (new Query($db))->from('users')->where(['role' => 'vip']);
$active->union($vip)->all();
$active->union($vip, all: true)->all(); // UNION ALL
foreach ((new Query($db))->from('users')->batch(1000) as $batch) {
// $batch is an array of up to 1000 rows
}
foreach ((new Query($db))->from('users')->each() as $row) {
// $row is a single row, fetched in batches internally
}
(new Query($db))->from('users')->where(['active' => false])
->chunkById(100, function (array $rows) use ($db) {
foreach ($rows as $row) {
$db->createCommand()->update('users', ['active' => true], ['id' => $row['id']])->execute();
}
});
// Custom primary key column
(new Query($db))->from('orders')->chunkById(500, $callback, 'order_id');
// Stop early by returning false
(new Query($db))->from('users')->chunkById(100, function (array $rows) {
// process...
return false; // stops after this chunk
});
// MySQL: INSERT ... ON DUPLICATE KEY UPDATE
// PostgreSQL: INSERT ... ON CONFLICT DO UPDATE
$db->createCommand()->upsert('users', [
'email' => '[email protected]',
'name' => 'John',
'login_count' => new Expression('login_count + 1'),
])->execute();
// Increment a single column
$db->createCommand()->increment('users', 'login_count', 1, ['id' => 1])->execute();
// Increment with extra columns to update
$db->createCommand()->increment('users', 'balance', 50.00, ['id' => 1], ['last_deposit' => '2024-01-15'])->execute();
// Decrement
$db->createCommand()->decrement('products', 'stock', 1, ['id' => 5])->execute();
// Increment multiple columns at once
$db->createCommand()->incrementEach('users', ['votes' => 5, 'balance' => 100], ['id' => 1])->execute();
use JustQuery\Expression\Value\Param;
use JustQuery\Constant\DataType;
$query->from('users')
->where('status = :status')
->params([':status' => 'active'])
->addParams([':role' => 'admin']);
// On commands with explicit type
$command = $db->createCommand('SELECT * FROM users WHERE id = :id');
$command->bindValue(':id', 42, DataType::INTEGER);
$command->bindValues([':name' => 'John', ':profile' => new Param($blob, DataType::LOB)]);
// Raw SQL
$row = $db->createCommand('SELECT * FROM users WHERE id = :id', [':id' => 1])->queryOne();
// Direct command query methods
$command = $db->createCommand('SELECT * FROM users');
$rows = $command->queryAll(); // All rows
$row = $command->queryOne(); // First row
$column = $command->queryColumn(); // First column as flat array
$value = $command->queryScalar(); // Single value
// SQL access
$command->getSql(); // The SQL with placeholders
$command->getRawSql(); // SQL with values inserted (for logging)
$command->setSql($sql); // Set new SQL (with quoting)
$command->setRawSql($sql); // Set SQL without modification
// Execute non-query (INSERT, UPDATE, DELETE)
$affectedRows = $command->execute();
$command = $db->createCommand('INSERT INTO orders ...');
$command->setRetryHandler(function (\JustQuery\Exception\Exception $e, int $attempt): bool {
// $attempt starts at 1
if ($attempt > 3) {
return false; // give up, throw the exception
}
// Retry on deadlock (MySQL error 1213)
return str_contains($e->getMessage(), 'Deadlock');
});
$command->execute();
// Force the optimizer to use a specific index
$query->from('users')
->forceIndex('users', 'idx_email')
->where(['email' => $email]);
// Suggest indexes (optimizer may still ignore)
$query->from('users')
->useIndex('users', ['idx_email', 'idx_status']);
// Prevent the optimizer from using an index
$query->from('users')
->ignoreIndex('users', 'idx_created_at');
// Multiple hints on the same table
$query->from('users')
->forceIndex('users', 'idx_email')
->ignoreIndex('users', 'idx_old_status');
// Hints on JOIN tables
$query->from('users')
->innerJoin('orders', 'users.id = orders.user_id')
->forceIndex('orders', 'idx_user_id');
// Works with aliases
$query->from(['u' => 'users'])
->forceIndex('users', 'idx_email')
->where(['u.email' => $email]);
use JustQuery\Expression\Expression;
$query->select([new Expression('COUNT(DISTINCT user_id) as unique_users')]);
$query->where(new Expression('DATE(created_at) = CURDATE()'));
use JustQuery\Expression\Statement\CaseX;
use JustQuery\Expression\Statement\WhenThen;
$case = new CaseX(
new WhenThen(['status' => 'active'], 'Active'),
new WhenThen(['status' => 'banned'], 'Banned'),
else: 'Unknown',
);
$query->select(['name', 'label' => $case]);
use JustQuery\Expression\Function\Greatest;
use JustQuery\Expression\Function\Least;
use JustQuery\Expression\Function\Length;
use JustQuery\Expression\Function\Longest;
use JustQuery\Expression\Function\Shortest;
// GREATEST / LEAST — returns max/min of multiple columns
$query->select([new Greatest('col1', 'col2', 'col3')]);
$query->select([new Least('price', 'sale_price')]);
// LENGTH — string length of a column
$query->select(['name', 'name_len' => new Length('name')]);
// LONGEST / SHORTEST — returns the longest/shortest string among columns
$query->select([new Longest('first_name', 'last_name')]);
$query->select([new Shortest('city', 'state')]);
use JustQuery\Expression\Value\JsonValue;
use JustQuery\Expression\Value\Value;
use JustQuery\Expression\Value\Param;
use JustQuery\Expression\Value\ColumnName;
use JustQuery\Expression\Value\DateTimeValue;
use JustQuery\Constant\DataType;
// JSON — encodes PHP array as JSON for the database
$query->where(['config' => new JsonValue(['role' => 'admin'])]);
// Value — wrap a raw PHP value for type-safe binding
$query->where(['=', 'score', new Value(42)]);
// Param — bind with explicit PDO data type
$query->where(['=', 'avatar', new Param($binaryData, DataType::LOB)]);
// ColumnName — reference a column name as an expression
$query->select([new ColumnName('users.name')]);
// DateTimeValue — bind DateTime objects
$query->where(['>=', 'created_at', new DateTimeValue(new \DateTimeImmutable('2024-01-01'))]);
use JustQuery\Expression\CompositeExpression;
// Group multiple expressions into one
$composite = new CompositeExpression('AND', [
new Expression('age > 18'),
new Expression('status = 1'),
]);
use JustQuery\Schema\Provider\{SchemaProvider, SchemaMode};
// DISABLED — pure query builder, no type casting
$provider = new SchemaProvider(SchemaMode::DISABLED);
// JSON — read from JSON files, zero DB overhead, deploy-safe
$provider = new SchemaProvider(SchemaMode::JSON, jsonPath: '/path/to/schema/');
// CACHE — read from DB, cache in Redis/APCu (traditional approach)
$provider = new SchemaProvider(SchemaMode::CACHE, dbSchema: $schema);
// JSON_CACHE — JSON first, DB+cache fallback for unknown tables
$provider = new SchemaProvider(SchemaMode::JSON_CACHE, dbSchema: $schema, jsonPath: '/path/to/schema/');
$row = $query->from('users')->where(['id' => 1])->withTypecasting()->one();
$row['id']; // int(1) — not string("1")
$row['is_active']; // bool(true) — not string("1")
$row['balance']; // float(1500.5) — not string("1500.50")
$row['options']; // array(...) — not string('{"role":"admin"}')
// Enable type casting only when reading from DB
$command = $db->createCommand('SELECT * FROM users');
$command = $command->withPhpTypecasting(); // cast DB → PHP on reads
// Enable type casting only when writing to DB
$command = $command->withDbTypecasting(); // cast PHP → DB on inserts/updates
// Enable both at once
$command = $command->withTypecasting();
// On Query objects
$query = (new Query($db))->from('users')->withTypecasting();
// Schema: score is GENERATED ALWAYS AS (reviews_sum / reviews_count)
$db->createCommand()->insert('products', [
'name' => 'Widget',
'price' => 9.99,
'score' => 4.5, // silently excluded — won't cause a MySQL error
])->execute();
use JustQuery\Profiler\QueryProfiler;
$profiler = new QueryProfiler();
$connection->setProfiler($profiler);
// ... run queries ...
$profiler->getCount(); // 47
$profiler->getTotalTime(); // 123.45 (ms)
$profiler->getSlowest(5); // top 5 slowest queries
$profiler->getErrors(); // queries that threw exceptions
$profiler->getQueries(); // all queries: sql, time, params, error
$profiler->reset(); // clear collected data
use JustQuery\Expression\Value\ArrayValue;
// ARRAY[1, 2, 3]::integer[]
$query->where(['=', 'ids', new ArrayValue([1, 2, 3], 'integer')]);
use JustQuery\Expression\Value\StructuredValue;
// ROW(10, 'USD')::money_type
$query->where(['=', 'price', new StructuredValue(['amount' => 10, 'currency' => 'USD'], 'money_type')]);
use JustQuery\Driver\Pgsql\Expression\Int4RangeValue;
use JustQuery\Driver\Pgsql\Expression\DateRangeValue;
// int4range '[1, 10)'
new Int4RangeValue(1, 10);
// daterange '[2024-01-01, 2024-12-31]'
new DateRangeValue(new DateTimeImmutable('2024-01-01'), new DateTimeImmutable('2024-12-31'));
use JustQuery\Expression\Function\ArrayMerge;
// Merge PostgreSQL arrays
$query->select([new ArrayMerge('tags1', 'tags2')]);
// CREATE INDEX ... USING GIN
$db->createCommand()->createIndex('users', 'idx_tags', 'tags', indexMethod: 'gin')->execute();
// Via Command (executes immediately)
$cmd = $db->createCommand();
// Via QueryBuilder (returns SQL string)
$qb = $db->getQueryBuilder();