db sandbox: show create table, more tests

This commit is contained in:
Spine
2025-05-22 08:12:26 +00:00
parent ba49e5bbc5
commit c2a8a04a2d
8 changed files with 83 additions and 24 deletions

View File

@@ -15,7 +15,8 @@ class PgInfo {
public function info(): array {
return $this->pg()->all("
select t.table_schema || '.' || t.table_name as table_name,
select (case when t.table_schema = 'public' then '' else t.table_schema || '.' end )
|| t.table_name as table_name,
pg_relation_size(t.table_schema || '.' || t.table_name) as table_size,
pg_indexes_size(t.table_schema || '.' || t.table_name) as index_size,
s.n_live_tup as live,

View File

@@ -44,8 +44,7 @@ class PgTable extends AbstractTable {
$this->pgro()->column("
with pkey as (
select cc.conrelid,
format(E',
constraint %I primary key(%s)', cc.conname,
format(E',\n constraint %I primary key(%s)', cc.conname,
string_agg(a.attname, ', '
order by array_position(cc.conkey, a.attnum))
) pkey
@@ -62,7 +61,7 @@ class PgTable extends AbstractTable {
case c.relpersistence when 't' then '' else n.nspname || '.' end,
c.relname,
string_agg(
format(E'\t%I %s%s',
format(E' %I %s%s',
a.attname,
pg_catalog.format_type(a.atttypid, a.atttypmod),
case when a.attnotnull then ' not null' else '' end

View File

@@ -6,43 +6,44 @@ declare(strict_types=1);
namespace Gazelle;
use Gazelle\Enum\SourceDB;
use Gazelle\Util\Text;
if (!$Viewer->permitted('admin_site_debug')) {
Error403::error();
}
$src = ($_REQUEST['src'] ?? SourceDB::mysql->value) == SourceDB::mysql->value
? SourceDB::mysql
: SourceDB::postgres;
$src = ($_REQUEST['src'] ?? Enum\SourceDB::mysql->value) == Enum\SourceDB::mysql->value
? Enum\SourceDB::mysql
: Enum\SourceDB::postgres;
$execute = false;
$execute = false;
$query = null;
$table = null;
$textAreaRows = 8;
if (isset($_GET['debug'])) {
$data = json_decode(Text::base64UrlDecode($_GET['debug']), true);
$data = json_decode(Text::base64UrlDecode($_GET['debug']), true);
$query = trim($data['query']);
if ($src === SourceDB::postgres && !empty($data['args'])) {
if ($src === Enum\SourceDB::postgres && !empty($data['args'])) {
$query .= "\n-- " . implode(', ', $data['args']);
}
$textAreaRows = max(8, substr_count($query, "\n") + 2);
} elseif (isset($_GET['table'])) {
$query = (new DB())->selectQuery($_GET['table']);
$textAreaRows = max(8, substr_count($query, "\n") + 2);
} elseif (!empty($_POST['query'])) {
$query = trim($_POST['query']);
$textAreaRows = max(8, substr_count($query, "\n") + 2);
$execute = true;
} else {
$query = null;
$table = $src === Enum\SourceDB::postgres
? new DB\PgTable($_GET['table'])
: new DB\MysqlTable($_GET['table']);
$textAreaRows = 8;
} elseif (!empty($_POST['query'])) {
$query = trim($_POST['query']);
$textAreaRows = max(8, substr_count($query, "\n") + 2);
$execute = true;
}
$error = false;
$result = [];
if ($execute) {
try {
if ($src == SourceDB::postgres) {
if ($src == Enum\SourceDB::postgres) {
$db = new \Gazelle\DB\Pg(PG_RO_DSN);
$result = $db->all($query);
} else {
@@ -60,5 +61,6 @@ echo $Twig->render('debug/db-sandbox.twig', [
'rows' => $textAreaRows,
'result' => $result,
'source' => $src->value,
'table' => $table,
'error' => $error,
]);

View File

@@ -10,7 +10,7 @@
<div class="pad"><div class="box pad">
<h3>Mysql table {{ table.name }} definition</h3>
<pre>{{ table.definition }}</pre>
<a href="tools.php?action=db_sandbox&amp;table={{ table.name }}" class="brackets">Inspect</a>
<a href="tools.php?action=db_sandbox&amp;src=mysql&amp;table={{ table.name }}" class="brackets">Inspect</a>
</div></div>
<div class="pad"><div class="box pad">

View File

@@ -30,7 +30,7 @@
<tr>
<td>{{ table.stats.live|number_format }}</td>
<td>{{ table.stats.dead|number_format }}</td>
<td>{{ (table.stats.dead == 0
<td>{{ (table.stats.live == 0
? 0
: (table.stats.dead / table.stats.live) * 100)|number_format(2)
}}</td>

View File

@@ -12,11 +12,13 @@
unsure how many rows will be returned. This is to avoid exhausting memory, as the resultset is buffered in memory.</div>
<div class="pad">If two columns have the same name (e.g. <code>ID</code>), only the first column will be displayed.</div>
<form action="tools.php?action=db_sandbox" method='POST'>
<textarea style="width: 98%;" name="query" cols="90" rows="{{ rows }}">{{ query }}</textarea><br /><br />
<textarea style="width: 98%;" name="query" cols="90" rows="{{ rows }}">{{ query }}</textarea>
Source
<label><input type="radio" name="src" value="mysql"{{ checked(source == 'mysql') }} /> Mysql</label>
<label><input type="radio" name="src" value="pg"{{ checked(source == 'pg') }} /> Postgres</label>
<input type="submit" value="Query" />
<br />
<div class="box pad"><pre>{{ table.definition }}</pre></div>
</form>
</div>

View File

@@ -134,6 +134,14 @@ class DbTest extends TestCase {
$this->assertCount(1, $warning, 'db-warning');
$this->assertEquals(1050, $warning[0]['code'], 'db-error-code');
$this->assertEquals("Table '$tableName' already exists", $warning[0]['message'], 'db-error-message');
$db->disableQueryLog();
$db->prepared_query('select now()');
$n = count($queryList);
$this->assertEquals($n, count($db->queryList()), 'db-query-log-off');
$db->enableQueryLog();
$db->prepared_query('select now()');
$this->assertEquals($n + 1, count($db->queryList()), 'db-query-log-on');
}
public function testGlobalStatus(): void {
@@ -287,6 +295,15 @@ class DbTest extends TestCase {
");
}
public function testMysqlMisc(): void {
$db = DB::DB();
$this->assertTrue(
$db->entityExists('users_main', 'Username'),
'db-mysql-entity-exists'
);
$this->assertEquals($db->info(), 'mysql via TCP/IP', 'db-mysql-info');
}
public function testMysqlTable(): void {
$bad = new DB\MysqlTable('nosuchtable');
$this->assertFalse($bad->exists(), 'mysql-table-does-not-exist');
@@ -487,6 +504,17 @@ class DbTest extends TestCase {
");
}
public function testMysqlPrepare(): void {
$db = DB::DB();
$this->assertInstanceOf(
\mysqli_stmt::class,
$db->prepare('select now()'),
'db-mysql-prepare-ok'
);
$this->expectException(\mysqli_sql_exception::class);
$db->prepare('this is not sql');
}
public function testPgWrapper(): void {
$this->pg()->execute("
create temporary table phpunit_pg_wrapper (
@@ -515,6 +543,33 @@ class DbTest extends TestCase {
);
}
public function testPgInsertCopy(): void {
$this->pg()->prepared_query("
create temporary table phpunit_insert_copy
(num int, name text, flag bool)
");
$output = [
['num' => 100, 'name' => "abc\ndef", 'flag' => true],
['num' => -100, 'name' => "ghi\tjkl", 'flag' => false],
['num' => 17, 'name' => null, 'flag' => 0],
['num' => null, 'name' => null, 'flag' => null],
];
$input = array_map(fn ($r) => array_values($r), $output);
$this->assertTrue(
$this->pg()->insertCopy(
'phpunit_insert_copy',
['num', 'name', 'flag'],
$input
),
'pg-insert-copy',
);
$this->assertEquals(
$output,
$this->pg()->all("select * from phpunit_insert_copy"),
'pg-read-insert',
);
}
public function testPgWrite(): void {
$this->expectException(\PDOException::class);
$this->expectExceptionMessageMatches(

View File

@@ -31,7 +31,7 @@ class PgInfoTest extends TestCase {
Enum\Direction::descending,
);
$list = $pgInfo->info();
$this->assertEquals('public.user_warning', $list[0]['table_name'], 'pginfo-list');
$this->assertEquals('user_warning', $list[0]['table_name'], 'pginfo-list');
}
public function testPgInfoColumn(): void {