Render Engine base-36 generator

Mike's Notes

The Render Engine (rnd) generates code (CFML, JAVA, Python, etc.), HTML, CSS, XML, JSON, YAML, SQL, PDFs, etc.  Many of these autonomous engines can run simultaneously, and they need to run much faster.

The Render Engine (rnd) is currently being rebuilt. A  base-36 generator is needed to create sequential 10-character strings using 0-9 and a-z.

Eg:

0000000001
0000000002
...
000000000a
...
000000000z
...
zzzzzzzzzzzz

These strings will be used in an automated batch process to create uniquely named "temp" directories to hold rendered output, directory, and file structures before final assembly and deployment. This setup can support 36 quadrillion of these temp directories. 😎

If needed, future major editions of Pipi, like "Pipi 15", could easily replace this generator with one that supports an even larger range. The other option is to recycle a pool of these strings, since they get deleted after use.

I used Google Search AI mode to generate some sample code and find some reading references. I asked lots of dumb questions and gave what-if prompts to explore options. It only took a few minutes to sort out a solution. And no token bill.

Below is an edited transcript, where I deleted all the dead ends to arrive at a workable, 100% reliable solution. The code is designed for PostgreSQL and CFML running on BoxLang on JDK 21.

It still needs testing. Remember, "Use code with caution." 😎😎😎😎

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

02/11/2026

Render Engine base-36 generator

By: Mike Peters & Google Search AI Mode
On a Sandy Beach: 01/11/2026

Mike Peters: Mike invented and designed Pipi and founded Ajabbi.

Google Search AI Mode: Free and works very well.

...

A base-36 10-character alphanumeric sequence (0-9, a-z) has over 3.6 quadrillion (36¹⁰) combinations

...

To generate base-36 alphanumeric sequences (0-9, a-z) natively in PostgreSQL, you can use PostgreSQL's procedural system. The math is more reliable and infinitely customizable if you first create a lightweight helper function in PostgreSQL, then execute it via a single <cfquery> block.

Here is how to set up and run the high-performance database-native approach:

Step 1: Run this once in PostgreSQL

Execute this script inside your database manager (like pgAdmin or DBeaver) to create a custom base-36 conversion function:

sql
CREATE OR REPLACE FUNCTION to_base36(digits bigint, min_width int DEFAULT 10) 
RETURNS text AS $$
DECLARE
    -- The complete alphanumeric sequence dictionary
    chars char[] := ARRAY['0','1','2','3','4','5','6','7','8','9',
                          'a','b','c','d','e','f','g','h','i','j',
                          'k','l','m','n','o','p','q','r','s','t',
                          'u','v','w','x','y','z'];
    ret text := '';
    val bigint := digits;
BEGIN
    -- Loop to convert decimal base-10 to base-36
    WHILE val > 0 LOOP
        ret := chars[(val % 36) + 1] || ret;
        val := val / 36;
    END LOOP;
    
    -- If the value was 0, it won't enter the loop
    IF ret = '' THEN ret := '0'; END IF;
    
    -- Dynamically left-pad with zeros to match our required 10-char width
    RETURN lpad(ret, min_width, '0');
END;
$$ LANGUAGE plpgsql IMMUTABLE;

Step 2: Your CFML Template Code

Once that helper function exists, your tag-based CFML file needs only one query block to populate thousands or millions of sequential records instantly:

cfml
<!--- Configuration --->
<cfset variables.datasource = "your_datasource_name">
<cfset variables.startValue = 0>
<cfset variables.endValue = 99999> <!--- This will instantly generate 100,000 unique records --->
<cfsetting requesttimeout="600">
<!--- Single high-performance multi-row native insert --->
<cfquery datasource="#variables.datasource#">
    INSERT INTO your_table_name (your_column_name)
    SELECT to_base36(val, 10)
    FROM generate_series(
        <cfqueryparam value="#variables.startValue#" cfsqltype="CF_SQL_BIGINT">, 
        <cfqueryparam value="#variables.endValue#" cfsqltype="CF_SQL_BIGINT">
    ) AS val
</cfquery>
<cfoutput>
    <p>Success! Generation complete across the database layer.</p>
</cfoutput>

Why this is the best option for PostgreSQL:

  • Blazing Fast: Generating 100,000 rows takes less than a second because generate_series() evaluates entirely within the database engine's cache memory.
  • Network efficiency: CFML passes exactly 2 integer values to the server (startValue and endValue). The database does all the structural formatting, heavy lifting, and inserts internally.
  • No Out-of-Memory Crashes: Traditional server-side loops building massive string payloads can crash your ColdFusion/Lucee JVM heap space if the array size gets too high. This setup bypasses CFML's memory limits entirely.

To meet your requirement of using each row once in sequence to create a directory and track its state, the best practice is to design a table that acts as a pre-generated "pool" of unique strings.

Here is the blueprint to create the table, pre-populate it, and safely fetch the next sequential directory name without race conditions.

Step 1: Create the Database Table

Run this script in your PostgreSQL database. It creates a table with an auto-incrementing serial index, the 10-character Base-36 string, and a boolean flag (is_used) to track whether it has been turned into a directory.

sql
CREATE TABLE directory_pool (
    id bigserial PRIMARY KEY,
    dir_name varchar(10) NOT NULL UNIQUE,
    is_used boolean DEFAULT false NOT NULL
);
-- Index the is_used and id columns for lightning-fast lookups
CREATE INDEX idx_pool_unused ON directory_pool (id) WHERE is_used = false;

Step 2: Pre-populate the Table Instantly

Using the to_base36 function we created earlier, you can populate your first batch of strings. For example, let's generate the first 100,000 sequential names (from 0000000000 to 0000002882 in Base-36) instantly:

cfml
<cfset variables.datasource = "your_datasource_name">
<cfset variables.startValue = 0>
<cfset variables.endValue = 99999>
<cfquery datasource="#variables.datasource#">
    INSERT INTO directory_pool (dir_name)
    SELECT to_base36(val, 10)
    FROM generate_series(
        <cfqueryparam value="#variables.startValue#" cfsqltype="CF_SQL_BIGINT">, 
        <cfqueryparam value="#variables.endValue#" cfsqltype="CF_SQL_BIGINT">
    ) AS val;
</cfquery>

Step 3: Fetch and Lock the Next Row Atomically

If multiple web requests try to create a directory at the exact same millisecond, they might grab the same name. To prevent this, use PostgreSQL's UPDATE ... RETURNING clause. This updates the flag and hands your CFML code the string in a single atomic action, blocking duplicate usage.

cfml
<cfset variables.datasource = "your_datasource_name">
<cfset variables.baseDirectory = "/var/www/uploads/directories/">

<!--- 
  Atomically claim the next unused sequence string.
  FOR UPDATE SKIP LOCKED ensures that if another thread is pulling a record,
  this thread ignores it and grabs the very next one cleanly.
--->
<cfquery name="qGetNextDir" datasource="#variables.datasource#">
    UPDATE directory_pool
    SET is_used = true
    WHERE id = (
        SELECT id 
        FROM directory_pool 
        WHERE is_used = false 
        ORDER BY id ASC 
        LIMIT 1
        FOR UPDATE SKIP LOCKED
    )
    RETURNING dir_name;
</cfquery>

<cfif qGetNextDir.recordCount IS 1>
    <cfset variables.newDirName = qGetNextDir.dir_name>
    <cfset variables.fullPath = variables.baseDirectory & variables.newDirName>
    
    <!--- Create the actual physical directory --->
    <cfdirectory action="create" directory="#variables.fullPath#">
    
    <cfoutput>
        <p>Successfully claimed sequence and created directory: <strong>#variables.newDirName#</strong></p>
    </cfoutput>
<cfelse>
    <p>Error: The directory pool is empty! You need to generate more records.</p>
</cfif>

Why this design works perfectly:

  • Strict Sequence Execution: Sorting by id ASC guarantees you will strictly consume them in sequence (0000000000, 0000000001, 0000000002, etc.).
  • Concurrency Safe: Using FOR UPDATE SKIP LOCKED makes this system enterprise-safe. Hundreds of users can trigger directory generation simultaneously, and nobody will ever get the same directory name or lock up the database.
  • Audit Trail: You can easily run a query to check how many names are remaining (SELECT count(*) FROM directory_pool WHERE is_used = false;).

No comments:

Post a Comment