rosavox/lib/alveolata/storage/implementation-sqltablecluster/functions.php
2025-05-23 07:33:29 +00:00

535 lines
13 KiB
PHP

<?php
namespace alveolata\storage;
// require_once(DIR_ALVEOLATA . '/definitions.php');
require_once(DIR_ALVEOLATA . '/map/functions.php');
require_once(DIR_ALVEOLATA . '/observer/wrapper-class.php');
require_once(DIR_ALVEOLATA . '/list/functions.php');
require_once(DIR_ALVEOLATA . '/storage/implementation-sqltable/functions.php');
/**
* exemplary structure:
* »CREATE TABLE core(id);«
* »CREATE TABLE lump(id, foo_id);«
*
* typical use case: for representing values with a type like "record<foo:string,bar:list<integer>>" in a relational
* database, one would create a core table for the primitive record fields (foo) and separate tables for all
* non-primitive fields (bar), which are linked to the core table
*
* @template type_value
* @template type_element
* @author Christian Fraß <frass@greenscale.de>
*/
class struct_sqltablecluster_tight_supplement
{
/**
* @var struct_sqltable
*/
public $target_table;
/**
* @var string
*/
public $core_id_column;
/**
* @var boolean
*/
public $include_own_id;
/**
* @var boolean
*/
public $exclude_core_id;
/**
*/
public function __construct(
struct_sqltable $target_table,
string $core_id_column,
bool $include_own_id,
bool $exclude_core_id
)
{
$this->target_table = $target_table;
$this->core_id_column = $core_id_column;
$this->include_own_id = $include_own_id;
$this->exclude_core_id = $exclude_core_id;
}
}
/**
* exemplary structure:
* »CREATE TABLE core(id);«
* »CREATE TABLE target(id);«
* »CREATE TABLE edge(core_id,target_id);«
*
* typical use case: domain B depends on domain A; elements of A can exist in ignorance of B
*
*
* @template type_value
* @template type_thing
* @author Christian Fraß <frass@greenscale.de>
*/
class struct_sqltablecluster_loose_supplement
{
/**
* @var struct_sqltable
*
* will only be used to add a deletion hook
*/
public $target_table;
/**
* @var struct_sqltable
*/
public $edge_table;
/**
* @var string
*
* name of the column in the edge table, which references the id in the core table
*/
public $core_id_column;
/**
* @var string
*
* name of the column in the edge table, which references the id in the target table
*/
public $target_id_column;
/**
*/
public function __construct(
struct_sqltable $target_table,
struct_sqltable $edge_table,
string $core_id_column,
string $target_id_column
)
{
$this->target_table = $target_table;
$this->edge_table = $edge_table;
$this->core_id_column = $core_id_column;
$this->target_id_column = $target_id_column;
}
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
class struct_sqltablecluster
{
/**
* @var struct_sqltable
*/
public $core;
/**
* @var array {map<string,struct_sqltablecluster_tight_supplement<type_value,any>>}
*/
public $tight_supplements;
/**
* @var array {map<string,struct_sqltablecluster_loose_supplement<type_value,any>>}
*/
public $loose_supplements;
/**
* @var \Closure {
* function<
* record<
* core_row:map<string,any>,
* tight_supplement_values:map<string,list<any>>,
* loose_supplement_values:map<string,list<integer>>
* >,
* type_value
* >
* }
*/
public $assemble;
/**
* @var \Closure {
* function<
* type_value,
* record<
* core_row:map<string,any>,
* tight_supplement_values:map<string,list<any>>,
* loose_supplement_values:map<string,list<integer>>
* >
* >
* }
*/
public $disperse;
/**
* @var \alveolata\observer\class_observer
*/
public $observer_delete;
/**
*/
public function __construct(
struct_sqltable $core,
array $tight_supplements,
array $loose_supplements,
\Closure $assemble,
\Closure $disperse
)
{
$this->core = $core;
$this->tight_supplements = $tight_supplements;
$this->loose_supplements = $loose_supplements;
$this->assemble = $assemble;
$this->disperse = $disperse;
$this->observer_delete = \alveolata\observer\class_observer::make();
}
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
function sqltablecluster_make(
struct_sqltable $core,
array $tight_supplements,
array $loose_supplements,
\Closure $assemble,
\Closure $disperse
) : struct_sqltablecluster
{
$subject = (
new struct_sqltablecluster(
$core,
$tight_supplements,
$loose_supplements,
$assemble,
$disperse
)
);
// hooks
{
// remove corresponding edges, when a supplemental dataset is deleted
{
foreach ($subject->loose_supplements as $loose_supplement_name => $loose_supplement) {
sqltable_hook_delete(
$loose_supplement->target_table,
function ($target_id) use ($loose_supplement) : void {
$loose_supplement_ids = sqltable_search(
$loose_supplement->edge_table,
[$loose_supplement->target_id_column => $target_id]
);
\alveolata\list_\map/*<int,void>*/(
$loose_supplement_ids,
function (int $loose_supplement_id) use ($loose_supplement) : void {
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id);
}
);
}
);
}
}
}
return $subject;
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
function sqltablecluster_hook_delete(
struct_sqltable $subject,
\Closure $procedure
) : string
{
return $subject->observer_delete->register($procedure);
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
function sqltablecluster_create(
struct_sqltablecluster $subject,
/*map<string,any> */$value
)/* : int*/
{
$dispersal = ($subject->disperse)($value);
$core_row = $dispersal['core_row'];
// core
{
$core_id = sqltable_create($subject->core, $core_row);
}
// tight supplements
{
$tight_supplement_ids = \alveolata\map\map/*<any,int>*/(
$subject->tight_supplements,
function ($tight_supplement, $tight_supplement_name) use ($dispersal, $core_id) {
return \alveolata\list_\map(
$dispersal['tight_supplement_values'][$tight_supplement_name],
function ($tight_supplement_row) use ($core_id, $tight_supplement) {
$tight_supplement_row[$tight_supplement->core_id_column] = $core_id;
return sqltable_create($tight_supplement->target_table, $tight_supplement_row);
}
);
}
);
}
// loose supplements
{
$loose_supplement_ids = \alveolata\map\map/*<any,int>*/(
$subject->loose_supplements,
function ($loose_supplement, $loose_supplement_name) use ($dispersal, $core_id) {
return \alveolata\list_\map(
$dispersal['loose_supplement_values'][$loose_supplement_name],
function (int $target_id) use ($core_id, $loose_supplement) {
$loose_supplement_row = [
$loose_supplement->core_id_column => $core_id,
$loose_supplement->target_id_column => $target_id,
];
return sqltable_create($loose_supplement->edge_table, $loose_supplement_row);
}
);
}
);
}
$key = $core_id;
return $key;
}
/**
* @author Christian Fraß <frass@greenscale.de>
* @todo improve update of satellites
*/
function sqltablecluster_update(
struct_sqltablecluster $subject,
/*int */$key,
/*map<string,any> */$value
) : void
{
$core_id = $key;
$dispersal = ($subject->disperse)($value);
$core_row = $dispersal['core_row'];
// core
{
sqltale_update($subject->core, $core_id, $core_row);
}
// tight supplements
{
\alveolata\map\map(
$subject->tight_supplements,
function ($tight_supplement, $tight_supplement_name) use ($core_id, $value, $dispersal) {
// remove old
{
$tight_supplement_ids_old = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
\alveolata\list_\map/*<int,void>*/(
$tight_supplement_ids_old,
function (int $tight_supplement_id_old) use ($tight_supplement) : void {
sqltable_delete($tight_supplement->target_table, $tight_supplement_id_old);
}
);
}
// insert new
{
$tight_supplement_ids_new = \alveolata\list_\map(
$dispersal['tight_supplement_values'][$tight_supplement_name],
function ($tight_supplement_row) use ($core_id, $tight_supplement) {
$tight_supplement_row[$tight_supplement->core_id_column] = $core_id;
return sqltable_create($tight_supplement->target_table, $tight_supplement_row);
}
);
}
}
);
}
// loose supplements
{
\alveolata\map\map(
$subject->loose_supplements,
function ($loose_supplement, $loose_supplement_name) use ($core_id, $value, $dispersal) {
// remove old
{
$loose_supplement_ids_old = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
\alveolata\list_\map/*<int,void>*/(
$loose_supplement_ids_old,
function (int $loose_supplement_id_old) use ($loose_supplement) : void {
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id_old);
}
);
}
// insert new
{
$loose_supplement_ids_new = \alveolata\list_\map(
$dispersal['loose_supplement_values'][$loose_supplement_name],
function (int $target_id) use ($core_id, $loose_supplement) {
$loose_supplement_row = [
$loose_supplement->core_id_column => $core_id,
$loose_supplement->target_id_column => $target_id,
];
return sqltable_create($loose_supplement->edge_table, $loose_supplement_row);
}
);
}
}
);
}
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
function sqltablecluster_delete(
struct_sqltablecluster $subject,
/*int */$key
) : void
{
// delete depedent data
{
$observation = $subject->observer_delete->notify($key);
}
$core_id = $key;
$core_row = sqltable_read($subject->core, $core_id);
// loose_supplements
{
\alveolata\map\map(
$subject->loose_supplements,
function (struct_sqltablecluster_loose_supplement $loose_supplement) use ($core_id) {
$loose_supplement_ids = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
return \alveolata\list_\map/*<int,void>*/(
$loose_supplement_ids,
function (int $loose_supplement_id) use ($loose_supplement) : void {
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id);
}
);
}
);
}
// tight_supplements
{
\alveolata\map\map(
$subject->tight_supplements,
function (struct_sqltablecluster_tight_supplement $tight_supplement) use ($core_id) {
$tight_supplement_ids = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
return \alveolata\list_\map/*<int,void>*/(
$tight_supplement_ids,
function (int $tight_supplement_id) use ($tight_supplement) : void {
sqltable_delete($tight_supplement->target_table, $tight_supplement_id);
}
);
}
);
}
// core
{
sqltable_delete($subject->core, $core_id);
}
}
/**
* @author Christian Fraß <frass@greenscale.de>
*/
function sqltablecluster_read(
struct_sqltablecluster $subject,
/*int */$key
)/* : map<string,any>*/
{
$core_id = $key;
// core
{
$core_row = sqltable_read($subject->core, $core_id);
}
// tight supplements
{
$tight_supplement_values = \alveolata\map\map(
$subject->tight_supplements,
function (struct_sqltablecluster_tight_supplement $tight_supplement) use ($core_id) {
$tight_supplement_ids = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
sort($tight_supplement_ids);
$value = \alveolata\list_\map/*<int,string>*/(
$tight_supplement_ids,
function (int $tight_supplement_id) use ($tight_supplement) {
$tight_supplement_row = sqltable_read($tight_supplement->target_table, $tight_supplement_id);
if ($tight_supplement->include_own_id) {
$tight_supplement_row['id'] = $tight_supplement_id;
}
if ($tight_supplement->exclude_core_id) {
unset($tight_supplement_row[$tight_supplement->core_id_column]);
}
return $tight_supplement_row;
}
);
return $value;
}
);
}
// loose supplements
{
$loose_supplement_values = \alveolata\map\map(
$subject->loose_supplements,
function (struct_sqltablecluster_loose_supplement $loose_supplement) use ($core_id) {
$loose_supplement_ids = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
sort($loose_supplement_ids);
$value = \alveolata\list_\map/*<int,string>*/(
$loose_supplement_ids,
function (int $loose_supplement_id) use ($loose_supplement) {
$loose_supplement_row = sqltable_read($loose_supplement->edge_table, $loose_supplement_id);
$target_id = $loose_supplement_row[$loose_supplement->target_id_column];
return $target_id;
}
);
return $value;
}
);
}
$stuff = [
'core_row' => $core_row,
'tight_supplement_values' => $tight_supplement_values,
'loose_supplement_values' => $loose_supplement_values,
];
$value = ($subject->assemble)($stuff);
return $value;
}
/**
* @author Christian Fraß <frass@greenscale.de>
* @todo search in satellites
*/
function sqltablecluster_search(
struct_sqltablecluster $subject,
array $parameters = []
) : array
{
return sqltable_search($subject->core, $parameters);
}
?>