james.maes 821632586e
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 18s
QRun-IO/ci-policy: native / native-result (push) Successful in 10s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 15s
maven / build (push) Successful in 2m15s
build / maven (push) Successful in 2m15s
Merge pull request 'ci: pin shared workflow to v1' (#27) from ci/pin-v1-main into main
2026-11-06 16:07:58 +00:00
2026-11-06 08:46:06 -06:00
2026-11-06 08:46:06 -06:00

QBit: Geo Data

Alpha CircleCI

Geographic reference data QBit providing countries, states/provinces, and cities for QQQ applications.

Quick Start

The current release is 0.2.0. It requires Java 21 and QQQ 4.1 (built against qqq-bom-pom 4.1.0-RC.21). See CHANGELOG.md.

Add the dependency to your pom.xml:

<dependency>
   <groupId>com.kingsrook.qbits</groupId>
   <artifactId>qbit-geo-data</artifactId>
   <version>0.2.0</version>
</dependency>

Releases are published to the QRun-IO Gitea Maven registry, which requires authentication for reads as well as writes. Add the repository and a matching <server> entry (with your own Gitea username and token) in your private Maven settings.xml:

<repository>
   <id>qrun-io</id>
   <url>https://git.kof22.com/api/packages/QRun-IO/maven</url>
   <releases><enabled>true</enabled></releases>
   <snapshots><enabled>false</enabled></snapshots>
</repository>

What You Get

This QBit provides three entities with pre-populated reference data:

Entity Fields Records
Country alpha2Code, alpha3Code, numericCode, name, officialName 250
StateProvince countryId, code, name, subdivisionType 5,296
City countryId, stateProvinceId, name, population, latitude, longitude 32,000+

Using this QBit

Step 1: Register the QBit

In your application's metadata setup, register the QBit with a table prefix:

import com.kingsrook.qbits.geodata.GeoDataQBitConfig;
import com.kingsrook.qbits.geodata.GeoDataQBitProducer;

new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("shipping"))
   .produce(qInstance, "shipping-geo");

This creates three tables in your database:

  • shipping_country
  • shipping_stateProvince
  • shipping_city

Step 2: Run the Sync Process

The QBit includes a sync process that populates tables from bundled JSON data. Run it once after creating your tables, or schedule it to pick up updates.

import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qbits.geodata.sync.GeoDataSyncProcessMetaDataProducer;
import com.kingsrook.qbits.geodata.sync.GeoDataSyncStep;

RunProcessInput input = new RunProcessInput();
input.setProcessName(GeoDataSyncProcessMetaDataProducer.NAME);
input.addValue(GeoDataSyncStep.FIELD_TABLE_NAME_PREFIX, "shipping");

new RunProcessAction().execute(input);

This syncs all three tables (countries, states, cities) in order.

Step 3: Query the Data

Use standard QQQ actions with the prefixed table names and the entity classes:

import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qbits.geodata.model.City;
import com.kingsrook.qbits.geodata.model.Country;
import com.kingsrook.qbits.geodata.model.StateProvince;

import static com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.*;

// Get a country by alpha-2 code
Country usa = new GetAction().executeForRecord(new GetInput("shipping_country")
   .withUniqueKey(Map.of("alpha2Code", "US")), Country.class);

// Query states for a country
List<StateProvince> states = new QueryAction().execute(new QueryInput("shipping_stateProvince")
   .withFilter(new QQueryFilter(new QFilterCriteria("countryId", EQUALS, usa.getId()))))
   .getRecordEntities(StateProvince.class);

// Query cities with population filter
List<City> cities = new QueryAction().execute(new QueryInput("shipping_city")
   .withFilter(new QQueryFilter()
      .withCriteria(new QFilterCriteria("stateProvinceId", EQUALS, state.getId()))
      .withCriteria(new QFilterCriteria("population", GREATER_THAN, 100000))))
   .getRecordEntities(City.class);

Key concept: The table name (e.g., "shipping_country") includes your prefix. The entity class (Country.class) is always the same regardless of prefix.

Step 4: Use in Dropdowns

Each entity is registered as a PossibleValueSource for use in form dropdowns:

import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;

new QFieldMetaData("countryId", QFieldType.INTEGER)
   .withPossibleValueSourceName("shipping_country")

Multiple Instances

Need geo data for different contexts? Register the QBit multiple times with different prefixes:

// For shipping addresses
new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("shipping"))
   .produce(qInstance, "shipping-geo");

// For billing addresses
new GeoDataQBitProducer()
   .withConfig(new GeoDataQBitConfig()
      .withBackendName("rdbms")
      .withTableNamePrefix("billing"))
   .produce(qInstance, "billing-geo");

This creates two independent sets of tables:

  • shipping_country, shipping_stateProvince, shipping_city
  • billing_country, billing_stateProvince, billing_city

Query each using its prefixed table name, same entity classes.

Database Schema

Generate a Liquibase changelog for your database:

import com.kingsrook.qbits.geodata.liquibase.GeoDataLiquibaseGenerator;

GeoDataQBitConfig config = new GeoDataQBitConfig()
   .withTableNamePrefix("shipping")
   .withEnableCountries(true)
   .withEnableStateProvinces(true)
   .withEnableCities(true);

GeoDataLiquibaseGenerator.generate(config, Path.of("db/changelog-geo-data.xml"));

The generator creates a changelog with your prefix substituted and only includes tables you've enabled.

License

AGPL-3.0 - See LICENSE

S
Description
Geographic reference data QBit for QQQ - countries, states/provinces, and cities
Readme Apache-2.0
6 MiB
0 Stars 3 Watchers 0 Forks
Release 0.1.0
Latest
2026-10-20 23:46:43 +00:00
Languages
Java 100%