AmberDB

 view release on metacpan or  search on metacpan

bin/setup_windows.ps1  view on Meta::CPAN

$taskName     = "AmberDB-Watchdog-$ProjectName"
$daemonScript = Join-Path $scriptDir "amberdb_daemon.pl"
$libDir       = Join-Path $appDir "lib"

# Subfolders required by AmberDB
$subdirs = @("tables", "config", "schema", "lock", "pids")

function Check-Admin {
    $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Error "[ERROR] Administrator privileges required! Please right-click PowerShell and select 'Run as Administrator'."
        exit 1
    }
}

function Install-ImDisk {
    Write-Host "[SETUP] Checking ImDisk installation..." -ForegroundColor Cyan

    $imdiskCmd = Get-Command imdisk -ErrorAction SilentlyContinue
    if ($imdiskCmd) {
        Write-Host "[OK] ImDisk is already installed on your system ($($imdiskCmd.Source))." -ForegroundColor Green

docs/EN.AmberDB-Locale_User-Guide.md  view on Meta::CPAN


| Capability | Description |
|---|---|
| Case conversion | Locale-aware `uc`, `lc`, `ucfirst` |
| Sorting | Unicode Collation Algorithm (UCA) based |
| ASCII transliteration | Convert accented characters to plain ASCII (slugs, IDs) |
| Number → Text | Written-out numbers for invoices/documents |
| Date/time formatting | Locale-specific date formats |
| Number/currency formatting | Grouping separators, decimal separators, symbol placement |
| HTML entity decoding | Named + numeric entity decode |
| Plural rules | CLDR-based plural form selection |
| UTF-8 safe substring | Character-based slicing (not byte-based) |

**Architectural principle:** Engine logic lives in `AmberDB::Locale.pm`, while language data resides in `AmberDB::Locale::Lang::*` packages as **pure data**. Engine and data are completely separated.

---

## 2. Architecture

```
AmberDB::Locale                 ← Main engine (all logic here)

docs/EN.AmberDB-vs-SQL_User-Guide.md  view on Meta::CPAN

> This guide is designed for software engineers coming from traditional relational database management systems (RDBMS / SQL) who want to quickly build applications with AmberDB. Rather than focusing on abstract theory or database philosophy, it adopt...

---

## Table of Contents

1. [Essential Practical Notes for Developers (Quick Intro)](#1-essential-practical-notes-for-developers-quick-intro)
2. [Basic CRUD Operations (DML)](#2-basic-crud-operations-dml)
   - [2.1 INSERT (Single Record)](#21-insert-single-record)
   - [2.2 BULK INSERT (Batch Ingestion)](#22-bulk-insert-batch-ingestion)
   - [2.3 SELECT by ID (Primary Key Point Read)](#23-select-by-id-primary-key-point-read)
   - [2.4 UPDATE by ID (Single Record Mutation)](#24-update-by-id-single-record-mutation)
   - [2.5 BULK UPDATE (Batch Mutation)](#25-bulk-update-batch-mutation)
   - [2.6 DELETE (Single Record Deletion & Soft-Delete)](#26-delete-single-record-deletion--soft-delete)
   - [2.7 BULK DELETE (Batch Deletion)](#27-bulk-delete-batch-deletion)
   - [2.8 COUNT(*) (Table Record Count)](#28-count-table-record-count)
3. [Querying, Filtering, and Search (SELECT, WHERE, LIKE)](#3-querying-filtering-and-search-select-where-like)
   - [3.1 Exact Match (WHERE field = value)](#31-exact-match-where-field--value)
   - [3.2 Multi-Value IN Lookup (WHERE id IN (...))](#32-multi-value-in-lookup-where-id-in-)
   - [3.3 Text Search (WHERE col LIKE '%...%' / FTS)](#33-text-search-where-col-like--fts)
   - [3.4 Compound Multi-Field Filtering (WHERE A = x AND B = y)](#34-compound-multi-field-filtering-where-a--x-and-b--y)
   - [3.5 Pagination (LIMIT & OFFSET)](#35-pagination-limit--offset)
4. [Sorting (ORDER BY) and Multilingual Collation](#4-sorting-order-by-and-multilingual-collation)
   - [4.1 Numeric and Text Sorting](#41-numeric-and-text-sorting)
   - [4.2 Multilingual and Turkish Character Collation](#42-multilingual-and-turkish-character-collation)
5. [Relationships and JOINs: The Core Architectural Difference](#5-relationships-and-joins-the-core-architectural-difference)
   - [5.1 SQL Normalized Multi-Table + JOIN Model](#51-sql-normalized-multi-table--join-model)

docs/EN.AmberDB_User-Guide.md  view on Meta::CPAN


Each block definition inside the `blocks` array supports the following attributes:

#### 9.7.1 Core Block Attributes

| Attribute | Type | Description | Example |
| :--- | :--- | :--- | :--- |
| `id` | `string` | Programmatic field identifier | `id => "email"` |
| `name` | `string` | Display label for UI forms and table headers | `name => "Email Address"` |
| `type` | `string` | Data storage, type validation, and indexing type | `type => "text"` |
| `input` | `string` | HTML/UI Form input component type | `input => "select"` |
| `valid` | `string` | Automated data validation rule | `valid => "not_null;email"` |
| `option` | `string` | Enumerated choice options (`value:label` pairs) | `option => "1:Active,0:Inactive"` |
| `rdbm` | `string / HASH`| Foreign table lookup mapping (`foreign_table;display_block`) | `rdbm => "catalog_category;2"` |
| `extend` | `HASH` | 1:1 vertical table extension | `extend => { table => "catalog_price", join => "id" }` |

#### 9.7.2 Supported 8 Core Field Types (`type`)

AmberDB uses **8 unified core storage types** across serialization (`db_encode`/`db_decode`), indexing, and sorting layers:

| Field Type (`type`) | Description | `enc_validate` (Write Phase) | `dec_validate` (Read Phase) | Indexing & Sorting Behavior |

docs/EN.AmberDB_User-Guide.md  view on Meta::CPAN


#### 9.7.3 UI Form Input Components (`input`)

Determines how the field is rendered in UI forms and administration panels:

| Component (`input`) | UI Element | Description |
| :--- | :--- | :--- |
| `text` | Text Input | Standard single-line text field `<input type="text">`. |
| `textarea` | Textarea | Multi-line plain text box `<textarea>`. |
| `summernote` | Summernote | Rich WYSIWYG HTML visual editor for articles/descriptions. |
| `select` | Dropdown Select | Single-selection dropdown list `<select>`. |
| `checkbox` | Checkbox | Multi-selection checkboxes `<input type="checkbox">` (Use `type => "num"` for Boolean). |
| `radio` | Radio Buttons | Single-selection radio options `<input type="radio">`. |
| `file` | File Upload | Attachment or image file uploader `<input type="file">`. |
| `hidden` | Hidden Field | Hidden form element `<input type="hidden">` (for primary IDs). |
| `email` | Email Input | HTML5 email input field `<input type="email">`. |
| `ascii` | ASCII Field | User/code input box constrained to ASCII charset. |
| `number` | Number Input | Numeric stepper `<input type="number">`. |
| `date` | Date Picker | Interactive date calendar selector `<input type="date">`. |
| `password` | Password Field | Obscured security input `<input type="password">`. |
| `repeat` / `repeats` | Repeater Table | Dynamic sub-row table input with add/remove row buttons (Order items, invoice lines). |
| `search_block` | Search Box | Search-assisted dynamic filter input. |
| `selectbyfind` | SelectByFind | Foreign relation selector populated via dynamic search. |
| `selectbylist` | SelectByList | Multi-item picker component from list. |

#### 9.7.4 Repeating Child Row Blocks (`repeat_start` and `repeat_ids`)

AmberDB natively supports dynamic repeating child rows (e.g. order line items, invoice product rows) horizontally across the flat parent record without relational child tables or `JOIN` operations:

- **Horizontal Row Slicing (`@record[15..$#record]`):** Repeating items, each field index beyond fixed blocks (`$record[15]`, `$record[16]`, `$record[17]`, ...) holds an individual repeating record item (e.g. `[ 101, 'Book', 2, '150.00' ]`).
- **`repeat_start`**: Specifies the starting block index where dynamic repeating rows begin (e.g. `repeat_start => 15`). In the schema, block 15 acts as the prototype template for all succeeding indices.
- **`repeat_ids`**: The engine (`repeat_fields`) scans `@record[15..$#record]`, extracts the first element (numeric item ID) of each repeating row, joins them with commas (`"101,102,103"`), and stores the string in `repeat_ids` (e.g. block 12). Inclu...

```perl

docs/EN.AmberDB_User-Guide.md  view on Meta::CPAN


# 5. HTML Entity Conversion (html_ascode / code_ashtml / text2html / html2text)
my $encoded_html = $adb->html_ascode('<a href="test">');   # Encodes special characters to HTML entities
my $plain_text   = $adb->html2text($html_document);
```

---

## 16. Faceted Search & Category Filters (Facet Engine)

The Facet Engine powers e-commerce sidebar filter menus (Brand, Category, Author, Price Range, Color, etc.), designed for high-performance, low-latency multi-select faceted filtering across large product catalogs.

### 16.1 Key Benefits & Features

* **Low-Latency Columnar Aggregation:** Instead of scanning full records across the entire database on every page view, the engine reads only the targeted columnar forward index files (`.fac`), aggregating filter menus with minimal I/O overhead.
* **Counts In-Stock & Active Items Only:** Discontinued, out-of-stock, or disabled products never inflate filter counts; shoppers see only genuine, purchasable options and accurate item counts.
* **Smart Multi-Select (Disjunctive Counting):** When a shopper selects multiple brands (e.g., both *Apple* and *Samsung*), remaining brand counts stay visible and accurate (OR logic within the group, AND logic across groups).
* **Search-Scoped Filters (`base_ids`):** When a visitor searches for a keyword (e.g., "wireless headphones"), the sidebar filter displays attributes only for the matching search results, rather than the entire store.
* **Automatic Label Resolution:** Numeric IDs and free-text attributes (e.g., Color names) are automatically resolved into human-readable UI labels without requiring manual join queries.

### 16.2 Schema Configuration (`.table`)

Enable the facet engine by adding `use_facet => 1` and your `facet_block` specifications to your table schema:

```perl
# dbstore/schema/catalog_attributes.table
{

docs/EN.AmberDB_User-Guide.md  view on Meta::CPAN

    ],
}
```

### 16.3 Usage & Practical Examples

#### A. Building Category Sidebar Menus
Generate complete filter groups and matching product counts in a single method call:

```perl
# User selections from URL query string: Category 5, Brand 12 or 14 selected
my %selected_filters = ( 1 => "5", 2 => ["12", "14"] );

my $menu = $adb->facet_menu(
    "catalog_attributes",
    \%selected_filters,
    $table_info->{facet_block},
    { limit => 10, sort => "count" } # Display top 10 options per group sorted by product count
);

# $menu structure is ready to pass directly to your template:
# {
#     count         => 42,                         # Total matching products
#     ids           => [ 101, 105, 120, ... ],     # IDs of matching products for product grid
#     active_counts => { 1 => 1, 2 => 2 },         # Active filters count per block
#     groups        => [                           # Ready-to-render sidebar groups:

docs/EN.AmberDB_User-Guide.md  view on Meta::CPAN

#### B. Dynamic Filters on Search Result Pages
Pass the list of search result IDs as `base_ids` so sidebar filters apply strictly to search results:

```perl
# 1. Search catalog for user query (keys_only returns unpaginated ID list)
my @found_ids = $adb->search_table("catalog_product", "sci-fi", keys_only => 1);

# 2. Generate facet menu scoped exclusively to the search results
my $search_facets = $adb->facet_menu(
    "catalog_attributes",
    \%selected_filters,
    $table_info->{facet_block},
    { base_ids => \@found_ids }
);
```

---

## 17. User Audit Trail and Backup

### 17.1 User Action History (`log_owner`)

docs/TR.AmberDB-vs-SQL_Kullanim_Rehberi.md  view on Meta::CPAN

> Bu kılavuz, geleneksel ilişkisel veritabanı (RDBMS / SQL) deneyimi olan geliştiricilerin AmberDB'ye hızla adapte olabilmesi için hazırlanmıştır. Kuramsal ve felsefi detaylar yerine, **"SQL'de şu şekilde yapılan işlem AmberDB'de şu ş...

---

## İçindekiler

1. [Geliştiricinin Bilmesi Gereken Temel Notlar (Hızlı Giriş)](#1-geliştiricinin-bilmesi-gereken-temel-notlar-hızlı-giriş)
2. [Temel CRUD İşlemleri (DML)](#2-temel-crud-işlemleri-dml)
   - [2.1 INSERT (Tekil Kayıt Ekleme)](#21-insert-tekil-kayıt-ekleme)
   - [2.2 BULK INSERT (Toplu Kayıt Ekleme)](#22-bulk-insert-toplu-kayıt-ekleme)
   - [2.3 SELECT by ID (Birincil Anahtarla Okuma)](#23-select-by-id-birincil-anahtarla-okuma)
   - [2.4 UPDATE by ID (Kayıt Güncelleme)](#24-update-by-id-kayıt-güncelleme)
   - [2.5 BULK UPDATE (Toplu Güncelleme)](#25-bulk-update-toplu-güncelleme)
   - [2.6 DELETE (Kayıt Silme & Soft-Delete)](#26-delete-kayıt-silme--soft-delete)
   - [2.7 BULK DELETE (Toplu Silme)](#27-bulk-delete-toplu-silme)
   - [2.8 COUNT(*) (Kayıt Sayısı)](#28-count-kayıt-sayısı)
3. [Sorgulama, Filtreleme ve Arama (SELECT, WHERE, LIKE)](#3-sorgulama-filtreleme-ve-arama-select-where-like)
   - [3.1 Tekil DeÄŸer EÅŸleÅŸmesi (WHERE field = value)](#31-tekil-deÄŸer-eÅŸleÅŸmesi-where-field--value)
   - [3.2 Çoklu Değer Listesi (WHERE id IN (...))](#32-çoklu-değer-listesi-where-id-in-)
   - [3.3 Metin Arama (WHERE col LIKE '%...%' / FTS)](#33-metin-arama-where-col-like--fts)
   - [3.4 Çok Kriterli Filtreleme (WHERE A = x AND B = y)](#34-çok-kriterli-filtreleme-where-a--x-and-b--y)
   - [3.5 Sayfalama (LIMIT & OFFSET)](#35-sayfalama-limit--offset)
4. [Sıralama (ORDER BY) ve Alfabetik Yerelleştirme (Collation)](#4-sıralama-order-by-ve-alfabetik-yerelleştirme-collation)
   - [4.1 Sayısal ve Alfabetik Sıralama](#41-sayısal-ve-alfabetik-sıralama)
   - [4.2 Türkçe ve Çok Dilli Karakter Sıralaması (Collation)](#42-türkçe-ve-çok-dilli-karakter-sıralaması-collation)
5. [İlişkiler ve JOIN Mantığı (En Büyük Mimari Fark)](#5-ilişkiler-ve-join-mantığı-en-büyük-mimari-fark)
   - [5.1 SQL Normalize Tablo + JOIN Modeli](#51-sql-normalize-tablo--join-modeli)

docs/TR.AmberDB_Veritabani_Sistemi.md  view on Meta::CPAN

    use_junk     => 1,                      
    junk_rules   => [ [ 11, "eq", 0 ] ],    
    use_ramdisk  => 1,                      
    ramdisk_ttl  => 3600,                   
    keep_deleted => 1,                      
    log_owner    => 1,                      
    min_char     => 2,                      
    
    blocks => [
        { id => "id",           name => "Ürün ID",     type => "auto_id", input => "hidden" },
        { id => "category_id",  name => "Kategori",    type => "text",    input => "select",   rdbm => "catalog_category;2" },
        { id => "brand_id",     name => "Marka",       type => "text",    input => "select",   rdbm => "catalog_brand;2" },
        { id => "author_id",    name => "Yazar",       type => "text",    input => "text" },
        { id => "title",        name => "Ürün Adı",    type => "text",    input => "text",     valid => "not_null" },
        { id => "subtitle",     name => "Alt Başlık",  type => "text",    input => "text" },
        { id => "supplier",     name => "Tedarikçi",   type => "text",    input => "text" },
        { id => "description",  name => "Açıklama",    type => "html",    input => "textarea" },
        { id => "stock",        name => "Stok Adedi",  type => "num",     input => "text" },
        { id => "barcode",      name => "Barkod",      type => "text",    input => "text" },
        { id => "price",        name => "Fiyat",       type => "num",     input => "text" },
        { id => "status",       name => "Satış Durumu",type => "option",  input => "select",   option => "1:Satışta,0:Pasif" },
    ],
}
```

### 9.6 Şema Parametreleri ve Konfigürasyon Referansı (Tablo Düzeyi)

Aşağıdaki tablo, bir `.table` dosyasında kullanılabilecek tüm üst düzey parametreleri, veri tiplerini, varsayılan değerlerini ve geriye dönük uyumluluk (eski sistem) karşılıklarını listeler:

| Parametre | Tip | Varsayılan | Eski / Alternatif Adı | Açıklama |
| :--- | :--- | :--- | :--- | :--- |

docs/TR.AmberDB_Veritabani_Sistemi.md  view on Meta::CPAN


Şema içindeki `blocks` dizisinde tanımlanan her bir alan bloğu şu nitelikleri alabilir:

#### 9.7.1 Temel Blok Nitelikleri

| Nitelik | Tip | Açıklama | Örnek |
| :--- | :--- | :--- | :--- |
| `id` | `string` | Alanın programatik anahtar adı | `id => "email"` |
| `name` | `string` | Formlarda ve tablolarda gösterilecek etiket adı | `name => "E-Posta Adresi"` |
| `type` | `string` | Veri depolama, tip doÄŸrulama ve indeksleme veri tipi | `type => "text"` |
| `input` | `string` | Form giriÅŸ bileÅŸeni (UI) tipi | `input => "select"` |
| `valid` | `string` | Otomatik doğrulama kuralı | `valid => "not_null;email"` |
| `option` | `string` | Seçenek listesi (`değer:etiket` çiftleri) | `option => "1:Aktif,0:Pasif"` |
| `rdbm` | `string / HASH`| Başka tablodan veri çekme (`hedef_tablo;gösterilecek_blok`) | `rdbm => "catalog_category;2"` |
| `extend` | `HASH` | 1:1 dikey geniÅŸletme tablosu | `extend => { table => "catalog_price", join => "id" }` |

#### 9.7.2 Desteklenen 8 Çekirdek Veri Tipi (`type`)

AmberDB motoru, serileştirme (`db_encode`/`db_decode`), indeksleme ve sıralama katmanlarında **8 temel çekirdek veri tipi** kullanır:

| Veri Tipi (`type`) | Tanım | `enc_validate` (Yazma Anı) | `dec_validate` (Okuma Anı) | İndeks ve Sıralama Davranışı |

docs/TR.AmberDB_Veritabani_Sistemi.md  view on Meta::CPAN


#### 9.7.3 Form GiriÅŸ BileÅŸenleri (`input`)

UI ve yönetim paneli katmanında form elemanının nasıl görüntüleneceğini belirler:

| Bileşen (`input`) | UI Elemanı | Açıklama |
| :--- | :--- | :--- |
| `text` | Metin Kutusu | Standart tek satırlık metin alanı `<input type="text">`. |
| `textarea` | Metin Alanı | Çok satırlı düz metin kutusu `<textarea>`. |
| `summernote` | Summernote | Zengin WYSIWYG görsel HTML editörü. |
| `select` | Açılır Menü | Tekli seçim kutusu `<select>`. |
| `checkbox` | Onay Kutusu | Çoklu seçim onay kutuları `<input type="checkbox">` (Boolean için `type => "num"`). |
| `radio` | Radyo Butonu | Tekli seçim radyo butonları `<input type="radio">`. |
| `file` | Dosya Yükleme | Dosya veya görsel yükleme bileşeni `<input type="file">`. |
| `hidden` | Gizli Alan | Gizli form elemanı `<input type="hidden">` (birincil ID için). |
| `email` | E-Posta Kutusu | HTML5 e-posta giriş alanı `<input type="email">`. |
| `ascii` | ASCII Alanı | Yalnızca ASCII karakterlere izin veren metin kutusu. |
| `number` | Sayı Kutusu | Sayısal giriş kutusu `<input type="number">`. |
| `date` | Tarih Seçici | Etkileşimli takvim tarih seçici `<input type="date">`. |
| `password` | Şifre Kutusu | Maskeli şifre giriş alanı `<input type="password">`. |
| `repeat` / `repeats` | Tekrarlayan Tablo | Dinamik alt satır ekleme/çıkarma formu (Sipariş kalemleri, fatura satırları). |
| `search_block` | Arama Kutusu | Arama destekli dinamik filtre giriş alanı. |
| `selectbyfind` | Arayarak Seç | İlişkili tablodan dinamik arama ile seçim bileşeni. |
| `selectbylist` | Listeden Seç | Listeden çoklu seçim bileşeni. |

#### 9.7.4 Tekrarlayan Alt Satır Blokları (`repeat_start` ve `repeat_ids`)

AmberDB, ilişkisel alt tablolara (child table) ve `JOIN` sorgularına ihtiyaç duymadan, ana kayıt içerisine gömülü tekrarlayan dinamik alt satırları (örn. sipariş kalemleri, fatura ürün satırları) yatay düzende doğrudan destekler:

- **Yatay Dizi Yapısı (`@record[15..$#record]`):** Tekrarlayan alt satırlar, sabit bloklardan sonra gelen her bir indeks (`$record[15]`, `$record[16]`, `$record[17]`, ...), bağımsız birer alt satır kaydıdır (örn: `[ 101, 'Kitap', 2, 150.00 ...
- **`repeat_start`**: Tekrarlayan dinamik blokların başladığı blok indeksini belirtir (örn. `repeat_start => 15`). Şemada 15. blok şablon olarak tanımlanır ve 15 ve sonraki tüm alanlar bu şablonun tip kurallarıyla doğrulanır.
- **`repeat_ids`**: Motor (`repeat_fields`), `@record[15..$#record]` dilimindeki tüm alt satırların birinci elemanını (sayısal ürün ID'si) otomatik olarak toplayıp virgülle birleştirir (`"101,102,103"`) ve `repeat_ids` (örn. 12) bloğuna ...

```perl

lib/AmberDB.pm  view on Meta::CPAN

    $adb->delete_list("catalog_product", 101, 102, 103);

=head2 read_id($table_id, [$record_id], [\%options])

Reads a single record by primary key ID (or dynamic positional type) in $O(1)$ time.

Options:

=over 4

=item * C<type>: Positional selector (C<'last'>, C<'first'>, C<'rand'>). When specified, a dummy ID (e.g. C<0>) can be passed to preserve standard 3-argument positional signature consistency: C<< $adb->read_id("products", 0, { type => "last" }) >> (o...

=item * C<sort>: Optional sort block (numeric index, schema block name like C<"price">, C<"price desc">, or hashref C<< { block => "price", dir => "asc" } >>). Used in combination with C<< type => 'first' >> or C<'last'> to retrieve the first or last...

=item * C<range>: Optional numerical/chronological range filter hashref C<< { block => 4, min => 1000, max => 2000 } >> to constrain candidate records before positional selection.

=item * C<inflate>: Boolean (C<1> or string C<"inflate">) to inflate record fields into a named HASH reference based on table schema blocks.

=item * C<counter> / C<use_counter>: Explicit boolean (C<1> or C<0>) or string C<"counter"> to force or disable incrementing the read counter (C<.cnt>).

=item * C<no_counter>: Explicit boolean (C<1>) or string C<"no_counter"> to suppress incrementing the read counter.

=item * C<deleted> / C<force>: Boolean (C<1>) or string C<"deleted"> / C<"force"> to read from soft-deleted archive (C<.del>) if missing from active table.

=item * C<links> / C<alias>: Boolean (C<1>) or string C<"links"> / C<"alias"> to resolve a deleted/merged record ID from alias link index (C<.lnk>) to its canonical record.

lib/AmberDB.pm  view on Meta::CPAN

    # Tiered Junk query mode
    my @active = $adb->field_fetch("products", 1, "5", { jnktype => 'A' }); # Only Active records

    # Numerical / chronological range filtering on an auxiliary block:
    my @range_prods = $adb->field_fetch("products", 2, "Smartphones", { range => { block => "price", min => 1000, max => 1500 } });

C<field_fetch> uses the C<match_block> definition in the schema and accesses inverted match index files (C<.fld>), providing $O(1)$ average-time lookup per indexed key (total retrieval cost scales with the number of requested values and matching reco...

=head2 search_table($table_id, $query, [\%options])

It performs searches matching query terms using the full-text C<.src> index (or a sorted table scan backup method if unindexed). C<search_table> uses the C<AmberDB::Locale> module. It features advanced language normalization according to the selected...

B<IMPORTANT (Return Signature Convention):>
When C<$limit> is passed and C<E<gt> 0> (paginated), C<search_table> returns C<($total_count, @records)> where the first scalar is the total matching count integer. When C<$limit> is omitted or C<0> (unpaginated), it returns C<@records> directly. Unp...

    # 1. Unpaginated (returns array of record arrayrefs directly)
    my @records        = $adb->search_table("catalog_product", "wireless headphones");
    my @sorted_records = $adb->search_table("catalog_product", "headphones", { sort => -5 });

    # 2. Paginated (first element is total matching count integer)
    my ($total_count, @search) = $adb->search_table("catalog_product", "headphones", { offset => 0, limit => 20 });

lib/AmberDB.pm  view on Meta::CPAN

            jnktype => 'AB',
        }
    );

    # Return only scalar record IDs
    my @all_ids             = $adb->search_table("catalog_product", "headphones", { keys_only => 1 });
    my ($total_count, @ids) = $adb->search_table("catalog_product", "headphones", { offset => 0, limit => 50, keys_only => 1 });

=head2 field_filter($table_id, \%filter_options)

Performs multi-block filtered queries (AND / OR) with support for multi-value filters, tier mode selection (C<jnktype>), numerical/chronological range filtering (C<range>), sorting, and pagination:

    my $res = $adb->field_filter("catalog_product", {
        type    => "and",
        filter  => { 1 => "5", 6 => ["12", "14"] },
        range   => { block => "price", min => 1000, max => 2500 },
        sort    => { blk => 5, reverse => 1 },
        jnktype => "AB",
        offset  => 0,
        limit   => 20,
    });

lib/AmberDB/Array.pm  view on Meta::CPAN


Calculates the dimensions of a list of array references (matrix). Returns a 2-element list: C<($max_row_index, $max_column_index)>.

  my ($max_row, $max_col) = $adb->array_size( [1, 2, 3], [4, 5] );
  # => (1, 2)  # 2 rows (0..1), 3 columns (0..2)

=head2 array_pick(\@indexes, @record)

Extracts and returns only the fields at the given 0-based index positions from C<@record>.

  my @selected = $adb->array_pick([ 0, 2 ], "ID101", "SecretKey", "PublicTitle");
  # => ("ID101", "PublicTitle")

=head2 deep_copy($data)

Recursively clones nested Perl data structures (hash references, array references, and scalar values) to produce an independent copy.

  my $copy = $adb->deep_copy({ user => { roles => [ "admin", "editor" ] } });

=head2 hash_diff($hash1, $hash2)

lib/AmberDB/Base/Facet.pm  view on Meta::CPAN

                $all_counts{$blk} = \%named_map;
            }
        }
    }

    return \%all_counts;
}

# Generates schema-driven facet menu structure and performs active filtering.
# my $result = $adb->facet_menu($tableid, [\%options]);
# options: selected => \%selected, facet_defs => \@facet_defs, offset => 0, limit => 20, base_ids => \@base_scope
# Legacy: my $result = $adb->facet_menu($tableid, \%selected, \@facet_defs, \%opts);
# ------------------------------------------------
sub facet_menu {

    my ( $self, $tableid, @args ) = @_;

    $tableid or return ( wantarray ? () : {} );
    my $table_info = $self->table_info($tableid);
    return ( wantarray ? () : {} ) unless $table_info && $table_info->{use_facet};

    my ( $selected, $facet_defs, $opts );
    if ( @args == 1 && ref( $args[0] ) eq 'HASH' ) {
        my $arg = $args[0];
        if ( exists $arg->{selected}
          || exists $arg->{facet_defs}
          || exists $arg->{offset}
          || exists $arg->{start}
          || exists $arg->{limit}
          || exists $arg->{base_ids}
          || exists $arg->{scope_ids}
          || exists $arg->{blocks}
          || exists $arg->{filter}
          || exists $arg->{where}
          || exists $arg->{match}
          || exists $arg->{range} )
        {
            $opts       = $arg;
            $selected   = $arg->{selected} || $arg->{filter} || $arg->{where} || $arg->{match} || {};
            $facet_defs = $arg->{facet_defs} || $arg->{blocks} || $table_info->{facet_block} || [];
        }
        else {
            $selected   = $arg;
            $facet_defs = $table_info->{facet_block} || [];
            $opts       = {};
        }
    }
    else {
        ( $selected, $facet_defs, $opts ) = @args;
    }

    $selected   ||= {};
    $facet_defs ||= $table_info->{facet_block} || [];
    $opts       ||= {};

    my $table_path = $self->table_path($tableid);
    my $offset     = $opts->{offset} // $opts->{start} // 0;
    my $limit      = $opts->{limit} // 0;
    my $base_scope = $opts->{base_ids} || $opts->{scope_ids} || undef;
    if ( $opts && $opts->{range} ) {
        if ( my $ranges = $self->normalize_range_opts( $tableid, $opts ) ) {
            if ( $base_scope && @$base_scope ) {

lib/AmberDB/Base/Facet.pm  view on Meta::CPAN

                    my $inx_path = "$table_path.inx";
                    ( undef, @all_active ) = $self->index_get( $inx_path, "keys" ) if -e $inx_path;
                }
                @all_active = $self->table_keys($tableid) unless @all_active;
                my @scoped = $self->filter_ids_by_range( $tableid, \@all_active, $ranges );
                $base_scope = \@scoped;
            }
        }
    }

    # Normalize active selections into %active_filter
    my %active_filter;
    for my $raw_k ( keys %$selected ) {
        my $blk = $raw_k;
        $blk =~ s/^f//; # Strip leading 'f' prefix if passed as f1, f2...
        my $v = $selected->{$raw_k};
        if ( defined $v && $v ne '' ) {
            my @vals = ref($v) eq 'ARRAY' ? @$v : split /,/, $v;
            @vals = grep { defined $_ && $_ ne '' } @vals;
            $active_filter{$blk} = \@vals if @vals;
        }
    }

    # 1. Active Filtering (Filtered IDs)
    my ( $filtered_ids, $total_count ) = ( [], 0 );
    if (%active_filter) {

lib/AmberDB/Base/Facet.pm  view on Meta::CPAN

            my $opt_str = $table_info->{blocks}->[$blk]->{option} // '';
            if ($opt_str) {
                for my $pair ( split /,/, $opt_str ) {
                    my ( $v, $l ) = split /:/, $pair, 2;
                    $name_map{$v} //= $l // $v;
                }
            }
        }

        # Active status for this block
        my %selected_vals = map { $_ => 1 } @{ $active_filter{$blk} // [] };
        my $active_cnt    = scalar keys %selected_vals;
        $active_counts{$blk} = $active_cnt;

        my @items;
        for my $val (@vals) {
            push @items, {
                uid     => "fc_${blk}_${val}",
                param   => "f$blk",
                val     => $val,
                label   => ( $name_map{$val} // $val ),
                count   => ( $counts->{$val} // 0 ),
                checked => ( $selected_vals{$val} ? "1" : "" ),
            };
        }

        my $group_data = {
            blk          => $blk,
            name         => $label,
            active       => ( $active_cnt ? "1" : "" ),
            active_count => $active_cnt,
            records      => \@items,
        };

lib/AmberDB/Base/Facet.pm  view on Meta::CPAN


AmberDB::Index::Facet - Column-oriented facet indexing, disjunctive counting, and navigation menu generator

=head1 SYNOPSIS

  # Querying from AmberDB instance ($adb inherits AmberDB::Index::Facet):

  # 1. Generate full-catalog or filtered facet menu with disjunctive counts:
  my $menu_data = $adb->facet_menu(
      "catalog_product",
      { 1 => "5", 2 => [ "12", "14" ] }, # %selected_filters
      \@facet_block_definitions,
      { sort => 'count', top => 10 }      # %options
  );

  # 2. Dynamic Scoped facet menu (e.g. within search results or category scope):
  my $search_facets = $adb->facet_menu(
      "catalog_product",
      \%selected,
      \@facet_defs,
      { base_ids => \@search_result_ids }
  );

  # 3. Direct facet key counts for a single block:
  my $counts = $adb->field_fltkeys("catalog_product", {
      target_block => 2,
      base_ids     => \@active_product_ids,
  });

lib/AmberDB/Base/Facet.pm  view on Meta::CPAN

=over 4

=item * B<1. Columnar Unified Storage (C<$table_path.fac>):> Facet data is stored in a unified columnar forward index file (C<$table_path.fac>). Each record's block values are keyed as C<$blk:$rid> mapping to packed value IDs, enabling fast single-co...

=item * B<2. Active-Only Storage Guarantee:> Facet index files store B<only currently active records>. Inactive, discontinued, or out-of-stock records violating C<facet_rules> / C<junk_rules> are excluded during indexing, eliminating the overhead of ...

=item * B<3. Bidirectional String Dictionary (C<.unq>):> Text facets (e.g. colors, specifications) map transparently between string labels and compact numeric dictionary IDs.

=item * B<4. Dynamic Scoping (C<base_ids>):> When computing facet counts within search results or subcategories, passing C<base_ids =E<gt> \@ids> bounds the aggregation strictly to matching records.

=item * B<5. Multi-Select Disjunctive Faceting:> Supports multi-selection where checking multiple items within the same filter group uses OR logic (showing counts of remaining options), while combining across different filter groups uses AND logic.

=back

=head1 METHODS

=head2 facet_menu($tableid, [\%options])

High-level faceted navigation menu generator.

Options:
=over 4
=item * C<selected>: Hash of currently active filter selections: C<{ block_idx =E<gt> $val_or_arr_ref }>. (Aliases: C<filter>, C<where>, C<match>).
=item * C<facet_defs>: Array of facet block definitions (or reads directly from table schema C<facet_block> if omitted).
=item * C<offset>: Pagination start offset (default: 0).
=item * C<limit>: Page size limit (default: 0 = unpaginated).
=item * C<base_ids>: (Alias: C<scope_ids>) Array reference of record IDs to scope calculation (e.g. search result IDs).
=item * C<sort>: C<'count'> (default, descending count) or C<'label'> / C<'name'> (alphabetical).
=item * C<top>: Limit maximum items returned per facet group (e.g. 10).
=item * C<min_count>: Minimum count required to include an item (default: 1).
=item * C<range>: Numerical / chronological range filtering C<{ block => 4, min => 1000, max => 2000 }>.
=back

Returns a comprehensive result hash:
C<{ count => $total, ids => \@filtered_ids, groups => \@groups, active_counts => \%counts, counts => \%all_counts }>.

  my $menu = $adb->facet_menu("catalog_product", {
      selected => { 1 => "5" },
      range    => { block => "price", min => 1000, max => 2000 },
      offset   => 0,
      limit    => 20,
  });

Legacy invocation C<$adb->facet_menu($tableid, \%selected, \@facet_defs, \%options)> remains fully supported.

=head2 field_fltkeys($tableid, \%opts)

Calculates facet key counts for a target block directly from active C<.fac>. Automatically resolves dictionary string labels.

Options:
=over 4
=item * C<target_block>: (Required) Attribute block index to aggregate facet counts for.
=item * C<filter>: (Optional, aliases: C<where>, C<match>) Active filter conditions on other blocks C<{ block_idx => $value }>.
=item * C<base_ids>: (Optional, alias: C<scope_ids>) Array reference of record IDs to scope calculation to.

lib/AmberDB/Base/Schema.pm  view on Meta::CPAN

            )
        };
        $schema->{use_simple}  = 1;
        $schema->{no_backup}   = 1;
        $schema->{no_transact} = 1;
        $schema->{ramdisk_ttl} = 300 unless defined $schema->{ramdisk_ttl} && $schema->{ramdisk_ttl} > 0;
    }

    # ------------------------------------------------------------------------
    # Pipeline Step 2: Simple Mode Stripping
    # In simple key-value mode, selectively strip columnar, indexing, relational,
    # and caching definitions to enforce lightweight operation.
    # ------------------------------------------------------------------------
    if ( $schema->{use_simple} && ( $schema->{use_ramdisk} // 0 ) != 3 ) {
        delete @{$schema}{
            qw(
              blocks match_block search_block view_block facet_block filter_block
              slug_block sort_block sort_fields use_facet facet_rules use_junk junk_rules
              record_index repeat_start repeat_ids field_rules
              use_ramdisk ramdisk_ttl use_cache cache_ttl
            )

lib/AmberDB/Locale.pm  view on Meta::CPAN


    # Safely evaluate numeric expression isolated from global DIE handlers
    my $res = eval {
        local $SIG{__DIE__} = sub {};
        eval $expr; ## no critic
    };
    return $res ? 1 : 0;
}

# -------------------------------------------------------
# Evaluate CLDR plural rule and select template.
#
# my $text = $lang->plural(1, { one => "{count} ürün", other => "{count} ürün" });
# my $text = $lang->plural(5, { one => "{count} item", other => "{count} items" });
# -------------------------------------------------------
sub plural {
    my ( $self, $count, $forms ) = @_;
    return '' unless defined $forms;

    $count //= 0;
    my $form_key = 'other';

lib/AmberDB/Locale.pm  view on Meta::CPAN


Custom formatting overrides:

  $tr->format_currency(100, symbol => 'TL', position => 'suffix', space => 1);
  # => "100,00 TL"

=head3 ISO 4217 Currency Dictionary

C<AmberDB::Locale> integrates a master dictionary of ISO 4217 currency definitions, numeric codes, currency symbols, and default subunit decimal precision (implemented internally via C<AmberDB::Locale::Currency>).

Direct dictionary lookups, symbol conversions, and select dropdown lists can be accessed via:

  use AmberDB::Locale::Currency;

  # Symbol and name lookups
  my $sym  = AmberDB::Locale::Currency->symbol('TRY'); # '₺'
  my $name = AmberDB::Locale::Currency->name('USD');   # 'US Dollar'
  my $info = AmberDB::Locale::Currency->by_code('EUR');
  # => { num => '978', name => 'Euro', symbol => '€', digits => 2 }

  # Dropdown options for UI forms

lib/AmberDB/Locale.pm  view on Meta::CPAN

Supported helper methods:

=over 4

=item * C<AmberDB::Locale::Currency-E<gt>by_code($iso_code)> - Returns the currency definition hash reference for the given 3-letter ISO 4217 code (case-insensitive), containing C<num>, C<name>, C<symbol>, and C<digits>.

=item * C<AmberDB::Locale::Currency-E<gt>symbol($iso_code)> - Returns the currency symbol for the given ISO code (e.g. C<'₺'>, C<'$'>, C<'€'>, C<'£'>, C<'₽'>, C<'¥'>). If the code is unknown, returns the uppercase code itself.

=item * C<AmberDB::Locale::Currency-E<gt>name($iso_code)> - Returns the English currency name for the given ISO code.

=item * C<AmberDB::Locale::Currency-E<gt>all()> - Returns a list of 2-element array references C<[ $code, $name ]> ordered by priority, suitable for rendering HTML C<E<lt>selectE<gt>> form dropdowns.

=item * C<AmberDB::Locale::Currency-E<gt>active_codes()> - Returns the list of active 3-letter ISO 4217 currency codes supported by the dictionary.

=back

=head2 Date & Time Operations

=head3 format_date($time_or_string [, $pattern_or_style])

Formats a Unix epoch timestamp or ISO date string into a localized date/time representation.

lib/AmberDB/Locale/Currency.pm  view on Meta::CPAN


# Get currency name by ISO code
# AmberDB::Locale::Currency->name('TRY') -> 'Türk Lirası'
sub name {
    my ( $class_or_self, $code ) = @_;
    return '' unless defined $code;
    my $c = $CURRENCIES{ uc($code) };
    return $c ? $c->{name} : uc($code);
}

# Get all currencies as [ [$code, $name], ... ] for form selects/dropdowns
sub all {
    return map { [ $_, $CURRENCIES{$_}->{name} ] } @CURRENCY_ORDER;
}

# List active ISO codes
sub active_codes {
    return @CURRENCY_ORDER;
}

1;

t/amberdb_ecommerce_facet.t  view on Meta::CPAN

);

$adb->insert_list( 'catalog_product', @products );

# ---------------------------------------------------------------------------
subtest '1. Initial Unfiltered Facet Menu & Label Resolution' => sub {
    plan tests => 8;

    my $menu = $adb->facet_menu(
        'catalog_product',
        {}, # No filter selected
        $adb->table_attr('catalog_product', 'facet_block'),
        { sort => 'count' }
    );

    ok( $menu, "Facet menu generated successfully" );
    is( $menu->{count}, 8, "Total active products count is 8 (junk item excluded)" );
    is( scalar( @{ $menu->{groups} } ), 4, "4 facet groups returned" );

    # Check Category Group (blk 1)
    my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };

t/amberdb_ecommerce_facet.t  view on Meta::CPAN

    is( $menu->{count}, 2, "Facet menu restricted to 2 search hits" );
    my ($cat_group) = grep { $_->{blk} == 1 } @{ $menu->{groups} };
    is( scalar( @{ $cat_group->{records} } ), 1, "Only Bilim Kurgu category present in search facets" );
    is( $cat_group->{records}->[0]->{label}, 'Bilim Kurgu', "Category is Bilim Kurgu" );
};

# ---------------------------------------------------------------------------
subtest '5. Dynamic Price Range (Min/Max Slider) & Facet Integration' => sub {
    plan tests => 5;

    # Scenario: User selects Price range [100.00 TL - 200.00 TL]
    # In catalog_product, prices are:
    # 1: 85.00  (Roman, Can, Kafka) -> OUT (< 100)
    # 2: 150.00 (Roman, Can, Kafka) -> IN
    # 3: 195.00 (Roman, Is Bankasi, Dostoyevski) -> IN
    # 4: 220.00 (Roman, Is Bankasi, Dostoyevski) -> OUT (> 200)
    # 5: 250.00 (Tarih, Is Bankasi, Ataturk) -> OUT (> 200)
    # 6: 75.00  (Tarih, Is Bankasi, Ataturk) -> OUT (< 100)
    # 7: 280.00 (Bilim Kurgu, Ithaki, Herbert) -> OUT (> 200)
    # 8: 160.00 (Bilim Kurgu, Ithaki, Herbert) -> IN

t/amberdb_facet_columnar.t  view on Meta::CPAN

            base_ids     => [ 101, 102 ],
        }
    );

    is( $scoped_brands->{'Apple'}, 1, "Scoped facet count: Apple = 1" );
    is( $scoped_brands->{'Samsung'}, 1, "Scoped facet count: Samsung = 1" );
    ok( !exists $scoped_brands->{'Dell'}, "Dell not in scope" );
};

# ---------------------------------------------------------------------------
subtest '4. facet_menu generation with selected filters' => sub {
    plan tests => 3;

    my $facet_menu = $adb->facet_menu(
        'catalog_product',
        { 1 => 'Telefon' }, # Filter by Telefon
        $adb->table_info('catalog_product')->{facet_block}
    );

    ok( ref($facet_menu) eq 'HASH', "facet_menu returned hash" );
    ok( exists $facet_menu->{groups_by_blk}, "groups_by_blk exists" );

t/amberdb_standard_api_args.t  view on Meta::CPAN

    my $all3 = $adb->field_allfltkeys('products', [ 2, 3 ], [ 1, 2 ]);
    is( $all3->{2}{'Smartphones'}, 2, 'Legacy field_allfltkeys with arrayref base_scope' );
};

# ---------------------------------------------------------------------------
subtest '9. facet_menu with standardized single \%options and legacy' => sub {
    plan tests => 6;

    # Standardized: single unified hashref
    my $menu1 = $adb->facet_menu('products', {
        selected => { 2 => 'Smartphones' },
        sort     => 'count'
    });
    ok( $menu1, 'facet_menu with unified hashref generated successfully' );
    is( $menu1->{count}, 2, 'Total matching count is 2' );
    is( scalar(@{ $menu1->{groups} }), 2, '2 facet groups in menu' );

    # Standardized: with offset & limit
    my $menu_paged = $adb->facet_menu('products', {
        offset => 0,
        limit  => 3



( run in 1.460 second using v1.01-cache-2.11-cpan-e623d60df62 )