diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f6a22f10..6d77355c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -57,6 +57,15 @@ jobs: - name: All Tests (tpchgen-cli) run: cargo test -p tpchgen-cli + # All tests for tpcdsgen + test-all-tpcdsgen: + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v6 + - name: All Tests (tpcdsgen) + run: cargo test -p tpcdsgen + # documentation build docs: runs-on: ubuntu-latest diff --git a/.github/workflows/tpcdsgen-conformance.yml b/.github/workflows/tpcdsgen-conformance.yml new file mode 100644 index 00000000..443b7695 --- /dev/null +++ b/.github/workflows/tpcdsgen-conformance.yml @@ -0,0 +1,114 @@ +name: TPC-DS Conformance + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + # Conformance testing against Java implementation + conformance-tests: + name: Conformance Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11' + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Bootstrap Java TPC-DS implementation + run: | + cd tpcdsgen + ./scripts/bootstrap-java.sh + + - name: Build Rust table generators + run: | + cargo build --release -p tpcdsgen + + - name: Generate test fixtures (Java reference data) + run: | + cd tpcdsgen + ./scripts/generate-fixtures.sh + + - name: Run conformance tests (Rust vs Java) + run: | + cd tpcdsgen + ./scripts/test-all-tables.sh + + - name: Upload test fixtures as artifacts + if: failure() # Upload fixtures if tests fail for debugging + uses: actions/upload-artifact@v4 + with: + name: test-fixtures + path: tpcdsgen/tests/fixtures/ + retention-days: 7 + + # Performance benchmarks (optional - only on main) + benchmarks: + name: Performance Benchmarks + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + needs: conformance-tests + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Build release binaries + run: | + cargo build --release -p tpcdsgen + + - name: Run benchmarks + run: | + # Generate all tables and time it using the unified CLI + mkdir -p /tmp/bench + time ./target/release/tpcdsgen --scale 1 --directory /tmp/bench + + - name: Upload benchmark results + uses: actions/upload-artifact@v4 + with: + name: benchmark-results + path: /tmp/bench/ + retention-days: 30 diff --git a/Cargo.toml b/Cargo.toml index 594e61ad..26f70304 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = [ "tpchgen" , "tpchgen-arrow", "tpchgen-cli"] +members = [ "tpchgen", "tpcdsgen" , "tpchgen-arrow", "tpchgen-cli"] resolver = "2" diff --git a/tpcdsgen/.gitignore b/tpcdsgen/.gitignore new file mode 100644 index 00000000..adcd66c8 --- /dev/null +++ b/tpcdsgen/.gitignore @@ -0,0 +1,13 @@ +/target +*.dat +*.txt + +# Patches and Java tree changes. +/patches/ + +# Test fixtures (generated). +#/tests/fixtures/ + +# Stuff I need to remember +NEXT_STEPS.md +ISSUES.md diff --git a/tpcdsgen/BUGS.md b/tpcdsgen/BUGS.md new file mode 100644 index 00000000..5cd67a29 --- /dev/null +++ b/tpcdsgen/BUGS.md @@ -0,0 +1,131 @@ +# Known Bugs and Implementation Details + +This is a list of documented "bugs" and implementation details that originate in the C +implementation and were preserved in the Java port and in this Rust port as well. + +It's necessary to keep the implementations bug for bug compatible for reproducibility +any deviations, even fixing some of the obvious bugs will produce different data and invalidate +benchmark results that users of this library will rely on. + +### 1. Quarter Sequence Calculation Bug + +**Location:** `DateDimRowGenerator.java:66` + +```java +int dQuarterSeq = (dYear - 1900) * 4 + dMoy / 3 + 1; +``` + +**Issue:** The formula uses `dMoy / 3` instead of `(dMoy - 1) / 3`, which incorrectly assigns: +- January (month 1): `1/3 = 0` → Q1 ✓ (correct) +- February (month 2): `2/3 = 0` → Q1 ✓ (correct) +- March (month 3): `3/3 = 1` → Q2 ✗ (should be Q1) +- April (month 4): `4/3 = 1` → Q2 ✓ (correct) + +This causes March to be incorrectly assigned to Q2 instead of Q1. + +### 2. Weekend Days Bug + +**Location:** `DateDimRowGenerator.java:76` + +```java +boolean dWeekend = dDow == 5 || dDow == 6; +``` + +**Issue:** Marks Friday (5) and Saturday (6) as weekend days instead of Saturday (6) and Sunday (0). This is inconsistent with standard calendar conventions where weekends are Saturday and Sunday. + +### 3. Current Day Comparison Bug + +**Location:** `DateDimRowGenerator.java:91` + +```java +boolean dCurrentDay = dDateSk == TODAYS_DATE.getDay(); +``` + +**Issue:** Compares `dDateSk` (Julian day number, e.g., 2452663) with `TODAYS_DATE.getDay()` (day of month, e.g., 8). These are incompatible values: +- `dDateSk` is the number of days since the Julian epoch +- `getDay()` returns the day of the month (1-31) + +This comparison will always be false unless by extreme coincidence the Julian day number happens to match a day of month (1-31). + +### 4. Leap Year Calculation Bug + +**Location:** `Date.java:100-105` + +```java +public static boolean isLeapYear(int year) +{ + // This is NOT a correct computation of leap years. + // There is a bug in the C code that doesn't handle century years correctly. + return year % 4 == 0; +} +``` + +**Issue:** The implementation only checks if a year is divisible by 4, ignoring the Gregorian calendar rules: +- Years divisible by 100 are NOT leap years (e.g., 1900) +- EXCEPT years divisible by 400 ARE leap years (e.g., 2000) + +The Java code itself acknowledges this is wrong but maintains it for compatibility with the C implementation. + +### 5. Last Day of Month Calculation Bug + +**Location:** `Date.java:computeLastDateOfMonth` + +```java +public static Date computeLastDateOfMonth(Date date) +{ + // copies a bug in the C code that adds all the days in the year + // through the first of month instead of just the number of days in the month + return fromJulianDays(toJulianDays(date) - date.day + getDaysThroughFirstOfMonth(date)); +} +``` + +**Issue:** The comment explicitly states this copies a bug from the C code. Instead of simply calculating the last day of the month, it performs a convoluted calculation involving days through the first of the month. + +### 6. Following Holiday Bug + +**Location:** `DateDimRowGenerator.java:78-82` + +```java +if (dayIndex == 1) { + // This is not correct-- the last day of the previous year is always the 366th day. + // Copying behavior of C code. + int lastDayOfPreviousYear = 365 + (isLeapYear(dYear - 1) ? 1 : 0); + dFollowingHoliday = getIsHolidayFlagAtIndex(lastDayOfPreviousYear) != 0; +} +``` + +**Issue:** The comment explicitly states "This is not correct" but maintains the bug for C code compatibility. The issue is that the last day of the previous year should always be day 366 in the distribution array, but the code calculates it as 365 or 366 based on whether the previous year was a leap year. + +## Implementation Notes + +### Constants + +The Java implementation uses these constants for date calculations: + +```java +public static final Date TODAYS_DATE = new Date(2003, 1, 8); // January 8, 2003 +public static final int CURRENT_QUARTER = 1; +public static final int CURRENT_WEEK = 2; +``` + +### Rust Implementation + +Our Rust implementation faithfully replicates all these bugs to ensure exact compatibility: + +```rust +// From date_dim_row_generator.rs + +// Replicate quarter sequence bug +let d_quarter_seq = (d_year - 1900) * 4 + d_moy / 3 + 1; + +// Replicate weekend days bug +let d_weekend = d_dow == 5 || d_dow == 6; // Friday or Saturday + +// Replicate current day comparison bug +let d_current_day = d_date_sk == TODAYS_DATE.day() as i64; // Bug: comparing julian to day of month + +// From date.rs - Replicate leap year bug +pub fn is_leap_year(year: i32) -> bool { + year % 4 == 0 // Intentionally wrong for compatibility +} +``` diff --git a/tpcdsgen/Cargo.toml b/tpcdsgen/Cargo.toml new file mode 100644 index 00000000..dfb45614 --- /dev/null +++ b/tpcdsgen/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tpcdsgen" +authors = ["clflushopt", "alamb"] +description = "TPC-DS data generation library." +repository = "https://github.com/clflushopt/tpchgen-rs" +version = "0.1.0" +edition = "2021" +default-run = "tpcdsgen" +license = { workspace = true } +homepage = { workspace = true } + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +chrono = "0.4" + +[[bin]] +name = "tpcdsgen" +path = "src/main.rs" diff --git a/tpcdsgen/README.md b/tpcdsgen/README.md new file mode 100644 index 00000000..623dff0b --- /dev/null +++ b/tpcdsgen/README.md @@ -0,0 +1,196 @@ +# TPC-DS Data Generator Crate + +This crate provides the core data generator logic for TPC-H. + +## Usage + +```bash +# Build the generator +cargo build --release + +# Generate all tables at scale factor 1 (default) +./target/release/tpcdsgen + +# Generate all tables at scale factor 10 +./target/release/tpcdsgen --scale 10 + +# Generate specific table +./target/release/tpcdsgen --table store_sales --scale 10 + +# Generate to a specific directory +./target/release/tpcdsgen --scale 10 --directory /path/to/output +``` + +## Generating Fixtures + +Fixtures are pre-generated TPC-DS data files used for conformance testing. + +### Directory Structure + +``` +tests/fixtures/ +├── java/ # Java reference implementation output +│ ├── scale-1/ # 25 tables, ~1.2GB +│ └── scale-10/ # 25 tables, ~11GB +└── rust/ # Rust implementation output + ├── scale-1/ # 25 tables, ~1.2GB + └── scale-10/ # 25 tables, ~11GB +``` + +### Generating Java Fixtures + +Requires the Java TPC-DS implementation to be built: + +```bash +# Build Java implementation (if not already built) +cd ../tpcds && mvn clean package -DskipTests && cd - + +# Generate Java fixtures for scale 1 +java -jar ../tpcds/target/tpcds-1.5-SNAPSHOT-jar-with-dependencies.jar \ + --scale 1 \ + --directory tests/fixtures/java/scale-1 \ + --overwrite + +# Generate Java fixtures for scale 10 +java -jar ../tpcds/target/tpcds-1.5-SNAPSHOT-jar-with-dependencies.jar \ + --scale 10 \ + --directory tests/fixtures/java/scale-10 \ + --overwrite +``` + +### Generating Rust Fixtures + +```bash +# Build Rust implementation +cargo build --release + +# Generate Rust fixtures for scale 1 +./target/release/tpcdsgen --scale 1 --directory tests/fixtures/rust/scale-1 + +# Generate Rust fixtures for scale 10 +./target/release/tpcdsgen --scale 10 --directory tests/fixtures/rust/scale-10 +``` + +### Conformance Testing + +To verify Rust output matches Java byte-for-byte: + +```bash +# Run conformance tests at scale 1 +./scripts/test-all-tables.sh --scale 1 + +# Run conformance tests at scale 10 +./scripts/test-all-tables.sh --scale 10 +``` + +See [HASHES.md](HASHES.md) for the canonical MD5 hashes. + +### Verifying Fixtures with MD5SUMS + +Each fixture directory contains an `MD5SUMS` file for verification. + +**On Linux:** +```bash +cd tests/fixtures/java/scale-1 +md5sum -c MD5SUMS +``` + +**On macOS:** +```bash +cd tests/fixtures/java/scale-1 +while read hash file; do + [[ $(md5 -q "$file") == "$hash" ]] && echo "$file: OK" || echo "$file: FAILED" +done < MD5SUMS +``` + +## Known Bugs + +The TPC-DS reference implementation contains several bugs that must be replicated for benchmark compliance. +These bugs originated in the C implementation and were faithfully reproduced in the Java port. Our Rust implementation +also replicates these bugs to ensure byte-for-byte compatibility with the reference implementation. + +See [BUGS.md](BUGS.md) for a detailed list of documented bugs, more will be added. + + +## TPC-DS Reference MD5 Hashes + +These are the canonical MD5 hashes for TPC-DS data generated by the Java reference implementation. +The Rust implementation must produce byte-for-byte identical output. + +## Scale 1 + +Generated with: `java -jar tpcds-*.jar --scale 1` + +| Table | MD5 Hash | +|-------|----------| +| call_center.dat | `cc9aabc63eb8603bd7330b6735ed0961` | +| catalog_page.dat | `0bbac1b8bdcf8ce2d5f0034980ee0196` | +| catalog_returns.dat | `8460b5abd6b6ceaf6107f217b016fb23` | +| catalog_sales.dat | `51a0bc401b4b64d94736634b54068240` | +| customer.dat | `3672ffdefac3cf00413ecef71a753636` | +| customer_address.dat | `abac2e3925ab9bf66cec3b527a0468ed` | +| customer_demographics.dat | `8831872c6d56ea9d4f24701f2feaef48` | +| date_dim.dat | `f3e77714328dcc57302777e72fd7747c` | +| dbgen_version.dat | `a430da74c2e44926c53deb74e35b23f1` * | +| household_demographics.dat | `dccf2ff17c5e420021fbf92bf9a0a5ec` | +| income_band.dat | `db8e8012be51ef81cf215774bec95533` | +| inventory.dat | `cfefc8724693ec9149f1d5b345fcecc2` | +| item.dat | `bebbcfd1acecdea16a5a3feb5e4deb96` | +| promotion.dat | `acb42558d0dc5e0ab6df5a664c1629cf` | +| reason.dat | `57fe9b8688095bd345cc846ec4400be0` | +| ship_mode.dat | `791d16af982a67ad170a6b6527e25a35` | +| store.dat | `80082d03e1b01340e19db3187d8edbd6` | +| store_returns.dat | `9009d804c02ee839e0b2ecd5fb4ae03f` | +| store_sales.dat | `f003b3810e042d6dd47f48506616d88d` | +| time_dim.dat | `a68339c5720d25380b53f6e0f2f72333` | +| warehouse.dat | `f56789e8b724b989d74e213e0686052f` | +| web_page.dat | `6feef91675c336d6f25e55ebbdf8c13c` | +| web_returns.dat | `e45390d32d1698fef71f05f474a4d748` | +| web_sales.dat | `15f9d835727f3a39a096c346f56e51f7` | +| web_site.dat | `de5fb00a80673cb44b4b508da75d4bcf` | + +## Scale 10 + +Generated with: `java -jar tpcds-*.jar --scale 10` + +| Table | MD5 Hash | +|-------|----------| +| call_center.dat | `235909679f4d125e769aa38eb16e9098` | +| catalog_page.dat | `a5daa0d93ecde8bd9f6ed79cd3b63916` | +| catalog_returns.dat | `982a8b96fa0d9487015cd137136c8f68` | +| catalog_sales.dat | `97d5351b430d6c15e3906518315f0787` | +| customer.dat | `486a030a55d468ef15ff2ff01583e6dc` | +| customer_address.dat | `860602fea368111009ef08b167e1e299` | +| customer_demographics.dat | `8831872c6d56ea9d4f24701f2feaef48` | +| date_dim.dat | `f3e77714328dcc57302777e72fd7747c` | +| dbgen_version.dat | `8553e926c33f4ad84e4d58fcfd20c48c` * | +| household_demographics.dat | `dccf2ff17c5e420021fbf92bf9a0a5ec` | +| income_band.dat | `db8e8012be51ef81cf215774bec95533` | +| inventory.dat | `4ad3640917c6567038f081bbe2cf0e3e` | +| item.dat | `bff29691c74ae66eb2dcc3af686fb2ba` | +| promotion.dat | `b8e8a7741f64edc5d09fdb0453c86705` | +| reason.dat | `a1fdcd35ca0eddd0d5f37b0e5c2fddb3` | +| ship_mode.dat | `791d16af982a67ad170a6b6527e25a35` | +| store.dat | `430a01467a2d55d0e9a1bebad4f1c44b` | +| store_returns.dat | `4ba001a6066db20066cd198242f92ca1` | +| store_sales.dat | `ecff92350fa0466e9b9407a1b5ad4020` | +| time_dim.dat | `a68339c5720d25380b53f6e0f2f72333` | +| warehouse.dat | `e0c56fe622774d09c9dec42029881ad5` | +| web_page.dat | `e55695fdb2b86f96cf46e2a55b6f3748` | +| web_returns.dat | `ac0197593d3f4cc3bb46c8ad7e6cd735` | +| web_sales.dat | `4da375300bcb0ce8785e1f100fb72efe` | +| web_site.dat | `4669d52e36cd112af10e137e5d8d7697` | + +\* `dbgen_version.dat` contains timestamps and will differ between runs. + +## Verification + +To verify the Rust implementation matches: + +```bash +# Verify at scale 1 +./scripts/test-all-tables.sh --scale 1 + +# Verify at scale 10 +./scripts/test-all-tables.sh --scale 10 +``` diff --git a/tpcdsgen/data/adjectives.dst b/tpcdsgen/data/adjectives.dst new file mode 100644 index 00000000..600c0c10 --- /dev/null +++ b/tpcdsgen/data/adjectives.dst @@ -0,0 +1,943 @@ +-- adjectives.dst based on the common word list +------ +-- call_center_class +-- values weights +-- ----------------------- +-- values weights +-- ======= ======= +-- 1. adjective 1.weight +------ +other:116 +new:108 +good:59 +old:49 +different:43 +local:41 +social:40 +small:40 +great:39 +important:36 +national:35 +british:33 +possible:32 +large:32 +political:29 +young:28 +public:28 +high:27 +available:26 +able:26 +full:24 +only:22 +major:22 +main:22 +economic:22 +real:21 +long:21 +likely:21 +international:21 +special:20 +particular:19 +difficult:19 +certain:19 +black:19 +big:19 +little:18 +general:18 +free:18 +clear:18 +white:17 +similar:17 +necessary:17 +english:17 +early:17 +central:17 +personal:16 +common:16 +true:15 +strong:15 +single:15 +recent:15 +private:15 +foreign:15 +financial:15 +american:15 +various:14 +royal:14 +poor:14 +open:14 +european:14 +whole:13 +simple:13 +right:13 +natural:13 +sure:12 +short:12 +modern:12 +legal:12 +human:12 +following:12 +final:12 +significant:11 +serious:11 +prime:11 +previous:11 +normal:11 +industrial:11 +current:11 +bad:11 +wrong:10 +successful:10 +specific:10 +red:10 +popular:10 +military:10 +low:10 +labour:10 +dead:10 +concerned:10 +basic:10 +appropriate:10 +alone:10 +useful:9 +traditional:9 +scottish:9 +professional:9 +present:9 +physical:9 +original:9 +individual:9 +happy:9 +fine:9 +effective:9 +easy:9 +considerable:9 +complete:9 +aware:9 +wide:8 +total:8 +responsible:8 +ready:8 +obvious:8 +medical:8 +left:8 +interesting:8 +independent:8 +heavy:8 +hard:8 +existing:8 +essential:8 +direct:8 +civil:8 +blue:8 +western:7 +top:7 +separate:7 +relevant:7 +regular:7 +practical:7 +powerful:7 +positive:7 +nuclear:7 +light:7 +late:7 +huge:7 +hot:7 +future:7 +french:7 +extra:7 +environmental:7 +complex:7 +commercial:7 +close:7 +chief:7 +beautiful:7 +annual:7 +active:7 +working:6 +very:6 +usual:6 +unable:6 +technical:6 +soviet:6 +sorry:6 +sexual:6 +rich:6 +religious:6 +regional:6 +primary:6 +ordinary:6 +nice:6 +male:6 +internal:6 +inc:6 +impossible:6 +fresh:6 +formal:6 +famous:6 +excellent:6 +domestic:6 +dark:6 +cultural:6 +average:6 +additional:6 +warm:5 +urban:5 +upper:5 +unlikely:5 +tiny:5 +suitable:5 +sufficient:5 +substantial:5 +strange:5 +soft:5 +senior:5 +scientific:5 +safe:5 +rural:5 +reasonable:5 +proper:5 +perfect:5 +past:5 +mental:5 +key:5 +joint:5 +interested:5 +immediate:5 +historical:5 +german:5 +familiar:5 +expensive:5 +equal:5 +empty:5 +educational:5 +due:5 +detailed:5 +democratic:5 +deep:5 +dangerous:5 +critical:5 +correct:5 +cold:5 +christian:5 +bright:5 +apparent:5 +alternative:5 +afraid:5 +actual:5 +wonderful:4 +wild:4 +vital:4 +vast:4 +unknown:4 +united:4 +unique:4 +typical:4 +thin:4 +tall:4 +subsequent:4 +standard:4 +southern:4 +severe:4 +secondary:4 +russian:4 +rare:4 +quiet:4 +potential:4 +permanent:4 +parliamentary:4 +official:4 +northern:4 +narrow:4 +minor:4 +massive:4 +long-term:4 +limited:4 +liberal:4 +level:4 +leading:4 +italian:4 +irish:4 +inner:4 +initial:4 +increased:4 +growing:4 +grey:4 +green:4 +fundamental:4 +front:4 +female:4 +far:4 +fair:4 +external:4 +extensive:4 +entire:4 +elderly:4 +eastern:4 +dry:4 +crucial:4 +criminal:4 +corporate:4 +contemporary:4 +constant:4 +careful:4 +capable:4 +busy:4 +broad:4 +brief:4 +attractive:4 +angry:4 +ancient:4 +academic:4 +yellow:3 +wooden:3 +willing:3 +widespread:3 +welsh:3 +weak:3 +voluntary:3 +visual:3 +video-taped:3 +valuable:3 +used:3 +unusual:3 +tired:3 +terrible:3 +temporary:3 +surprising:3 +supreme:3 +sudden:3 +subject:3 +statutory:3 +spanish:3 +solid:3 +slow:3 +silent:3 +sharp:3 +sensitive:3 +sad:3 +running:3 +rough:3 +remarkable:3 +remaining:3 +relative:3 +rapid:3 +quick:3 +pure:3 +proposed:3 +pale:3 +overall:3 +odd:3 +numerous:3 +negative:3 +married:3 +lucky:3 +lovely:3 +living:3 +literary:3 +keen:3 +just:3 +japanese:3 +indian:3 +healthy:3 +guilty:3 +golden:3 +global:3 +glad:3 +genuine:3 +friendly:3 +firm:3 +federal:3 +expected:3 +exciting:3 +enormous:3 +emotional:3 +electronic:3 +efficient:3 +dramatic:3 +double:3 +distinct:3 +dependent:3 +daily:3 +conventional:3 +conservative:3 +confident:3 +comprehensive:3 +competitive:3 +coming:3 +comfortable:3 +clean:3 +classical:3 +capital:3 +brown:3 +back:3 +alive:3 +agricultural:3 +african:3 +administrative:3 +adequate:3 +acceptable:3 +absolute:3 +written:2 +wet:2 +vulnerable:2 +visible:2 +violent:2 +victorian:2 +valid:2 +urgent:2 +universal:2 +unexpected:2 +unemployed:2 +underlying:2 +ultimate:2 +trying:2 +tough:2 +tory:2 +thick:2 +theoretical:2 +sweet:2 +surprised:2 +stupid:2 +strategic:2 +statistical:2 +spiritual:2 +specialist:2 +sound:2 +sophisticated:2 +sole:2 +so-called:2 +smooth:2 +slight:2 +silver:2 +silly:2 +sensible:2 +secret:2 +satisfactory:2 +round:2 +roman:2 +revolutionary:2 +residential:2 +remote:2 +reliable:2 +related:2 +raw:2 +rational:2 +radical:2 +psychological:2 +proud:2 +principal:2 +pretty:2 +presidential:2 +pregnant:2 +precise:2 +pleasant:2 +pink:2 +patient:2 +overseas:2 +outstanding:2 +outside:2 +outer:2 +organic:2 +opposite:2 +occasional:2 +net:2 +nervous:2 +mutual:2 +musical:2 +multiple:2 +moving:2 +moral:2 +monetary:2 +modest:2 +minute:2 +middle:2 +mere:2 +mean:2 +maximum:2 +material:2 +mass:2 +marginal:2 +mad:2 +logical:2 +live:2 +linguistic:2 +like:2 +liable:2 +known:2 +junior:2 +judicial:2 +jewish:2 +isolated:2 +involved:2 +intense:2 +informal:2 +inevitable:2 +increasing:2 +inadequate:2 +impressive:2 +ill:2 +illegal:2 +identical:2 +ideal:2 +honest:2 +holy:2 +historic:2 +helpful:2 +head:2 +grateful:2 +grand:2 +gold:2 +given:2 +gentle:2 +generous:2 +gastric:2 +fun:2 +funny:2 +frequent:2 +forward:2 +flexible:2 +flat:2 +fixed:2 +favorite:2 +fat:2 +fast:2 +false:2 +extreme:2 +extraordinary:2 +experimental:2 +exact:2 +evident:2 +everyday:2 +ethnic:2 +electric:2 +electrical:2 +electoral:2 +dominant:2 +distinctive:2 +distant:2 +disabled:2 +dirty:2 +determined:2 +desperate:2 +desirable:2 +dear:2 +deaf:2 +curious:2 +creative:2 +controversial:2 +continuous:2 +constitutional:2 +consistent:2 +conscious:2 +clinical:2 +chinese:2 +chemical:2 +cheap:2 +catholic:2 +broken:2 +brilliant:2 +bloody:2 +blind:2 +bitter:2 +bare:2 +awful:2 +automatic:2 +australian:2 +assistant:2 +asleep:2 +armed:2 +anxious:2 +advanced:2 +adult:2 +acute:2 +accurate:2 +worthy:1 +worthwhile:1 +worried:1 +working-class:1 +wise:1 +well-known:1 +welcome:1 +weekly:1 +wealthy:1 +waste:1 +waiting:1 +vocational:1 +vertical:1 +variable:1 +vague:1 +useless:1 +unpleasant:1 +unnecessary:1 +unlike:1 +unhappy:1 +unfair:1 +uncomfortable:1 +unchanged:1 +uncertain:1 +unaware:1 +unacceptable:1 +ugly:1 +twin:1 +turkish:1 +tropical:1 +tremendous:1 +tragic:1 +toxic:1 +tight:1 +thorough:1 +thinking:1 +territorial:1 +tender:1 +teenage:1 +technological:1 +systematic:1 +sympathetic:1 +symbolic:1 +swiss:1 +suspicious:1 +supposed:1 +super:1 +superior:1 +superb:1 +successive:1 +subtle:1 +subjective:1 +structural:1 +strict:1 +straight:1 +straightforward:1 +still:1 +steep:1 +steady:1 +static:1 +star:1 +stable:1 +square:1 +splendid:1 +spectacular:1 +specified:1 +spatial:1 +spare:1 +solar:1 +socialist:1 +smart:1 +slim:1 +skilled:1 +sick:1 +shy:1 +short-term:1 +sheer:1 +shared:1 +shallow:1 +semantic:1 +select:1 +selective:1 +secure:1 +satisfied:1 +sacred:1 +ruling:1 +routine:1 +romantic:1 +rigid:1 +ridiculous:1 +retail:1 +resulting:1 +respective:1 +respectable:1 +required:1 +representative:1 +reluctant:1 +regulatory:1 +redundant:1 +reduced:1 +realistic:1 +random:1 +racial:1 +psychiatric:1 +provincial:1 +protective:1 +prospective:1 +prominent:1 +progressive:1 +profound:1 +profitable:1 +productive:1 +probable:1 +primitive:1 +prepared:1 +premier:1 +preliminary:1 +precious:1 +post-war:1 +polite:1 +polish:1 +pleased:1 +planned:1 +plain:1 +philosophical:1 +persistent:1 +peculiar:1 +peaceful:1 +payable:1 +passive:1 +partial:1 +part-time:1 +parental:1 +palestinian:1 +painful:1 +outdoor:1 +orthodox:1 +organisational:1 +orange:1 +oral:1 +optimistic:1 +operational:1 +opening:1 +olympic:1 +old-fashioned:1 +occupational:1 +objective:1 +novel:1 +notable:1 +noble:1 +neutral:1 +neighbouring:1 +neat:1 +nearby:1 +naval:1 +native:1 +nasty:1 +naked:1 +mysterious:1 +monthly:1 +molecular:1 +model:1 +mixed:1 +misleading:1 +miserable:1 +minimum:1 +minimal:1 +mild:1 +middle-class:1 +metropolitan:1 +medium:1 +mediterranean:1 +medieval:1 +mechanical:1 +mature:1 +mathematical:1 +marvellous:1 +marked:1 +marine:1 +manufacturing:1 +managerial:1 +magnificent:1 +magnetic:1 +magic:1 +ltd.:1 +loyal:1 +lost:1 +loose:1 +lonely:1 +lively:1 +linear:1 +lexical:1 +lesser:1 +lengthy:1 +legitimate:1 +legislative:1 +large-scale:1 +lacking:1 +kind:1 +islamic:1 +irrelevant:1 +invisible:1 +intimate:1 +intermediate:1 +interior:1 +intensive:1 +intelligent:1 +intellectual:1 +integrated:1 +intact:1 +insufficient:1 +institutional:1 +innocent:1 +inland:1 +inherent:1 +influential:1 +indirect:1 +incredible:1 +inappropriate:1 +improved:1 +implicit:1 +imperial:1 +immense:1 +imaginative:1 +ideological:1 +hungry:1 +hostile:1 +horrible:1 +horizontal:1 +hidden:1 +harsh:1 +handsome:1 +gross:1 +grim:1 +greek:1 +gradual:1 +gothic:1 +glorious:1 +giant:1 +geographical:1 +genetic:1 +gay:1 +furious:1 +functional:1 +full-time:1 +fortunate:1 +forthcoming:1 +formidable:1 +fond:1 +fit:1 +fiscal:1 +fierce:1 +fellow:1 +favorable:1 +fatal:1 +fashionable:1 +fascinating:1 +faint:1 +extended:1 +explicit:1 +expert:1 +experienced:1 +exotic:1 +executive:1 +exclusive:1 +excessive:1 +exceptional:1 +evolutionary:1 +evil:1 +eventual:1 +ethical:1 +estimated:1 +established:1 +equivalent:1 +enthusiastic:1 +endless:1 +encouraging:1 +empirical:1 +eligible:1 +elegant:1 +elected:1 +elaborate:1 +eager:1 +dynamic:1 +dutch:1 +dull:1 +dual:1 +dreadful:1 +doubtful:1 +divine:1 +diverse:1 +distinguished:1 +disciplinary:1 +disastrous:1 +diplomatic:1 +digital:1 +developing:1 +demanding:1 +delightful:1 +delighted:1 +delicious:1 +delicate:1 +deliberate:1 +definite:1 +defensive:1 +decisive:1 +decent:1 +darling:1 +damp:1 +cruel:1 +crude:1 +crazy:1 +costly:1 +corresponding:1 +cool:1 +convincing:1 +convenient:1 +contrary:1 +continuing:1 +continued:1 +continental:1 +content:1 +confidential:1 +concrete:1 +compulsory:1 +complicated:1 +competent:1 +compatible:1 +comparative:1 +comparable:1 +communist:1 +combined:1 +colourful:1 +coloured:1 +colonial:1 +collective:1 +coherent:1 +cognitive:1 +coastal:1 +closed:1 +clever:1 +classic:1 +chronic:1 +chosen:1 +cheerful:1 +charming:1 +changing:1 +cautious:1 +causal:1 +casual:1 +capitalist:1 +canadian:1 +burning:1 +brave:1 +bottom:1 +boring:1 +bold:1 +blank:1 +bizarre:1 +biological:1 +beneficial:1 +base:1 +awkward:1 +autonomous:1 +atomic:1 +atlantic:1 +associated:1 +asian:1 +ashamed:1 +artistic:1 +artificial:1 +arbitrary:1 +arab:1 +appointed:1 +applicable:1 +anonymous:1 +ambitious:1 +amazing:1 +alleged:1 +aggressive:1 +advisory:1 +adverse:1 +adjacent:1 +added:1 +accused:1 +accessible:1 +abstract:1 +absent:1 +above:1 diff --git a/tpcdsgen/data/adverbs.dst b/tpcdsgen/data/adverbs.dst new file mode 100644 index 00000000..926ab675 --- /dev/null +++ b/tpcdsgen/data/adverbs.dst @@ -0,0 +1,933 @@ +-- +-- adverbs +-- from the common word list +-- values weights +-- ======= ======= +-- 1. adverb 1. weight +------ +then:619 +more:615 +also:592 +so:540 +now:538 +only:524 +as:436 +very:431 +just:426 +even:329 +still:318 +too:316 +however:280 +there:269 +most:263 +here:263 +much:257 +never:241 +again:236 +well:216 +always:199 +often:178 +about:171 +already:156 +yet:151 +perhaps:151 +almost:151 +quite:150 +really:149 +later:141 +all:133 +both:129 +of course:126 +at least:119 +less:117 +ever:114 +enough:114 +for example:113 +together:111 +therefore:107 +over:106 +probably:105 +today:102 +thus:101 +particularly:101 +far:100 +rather:95 +further:94 +sometimes:91 +yesterday:87 +usually:87 +indeed:85 +ago:85 +simply:83 +especially:83 +else:79 +home:78 +certainly:77 +once:75 +soon:74 +clearly:70 +either:68 +long:66 +away:66 +better:65 +actually:65 +finally:62 +at all:62 +in order:61 +recently:57 +quickly:57 +suddenly:56 +generally:54 +nearly:50 +above:50 +more than:49 +forward:49 +right:47 +please:46 +easily:46 +immediately:45 +highly:44 +earlier:44 +early:43 +no longer:42 +fully:42 +exactly:42 +eventually:42 +directly:42 +hardly:41 +best:41 +below:41 +around:41 +slightly:40 +alone:40 +otherwise:39 +obviously:39 +a little:39 +slowly:38 +completely:38 +relatively:37 +merely:36 +maybe:36 +high:36 +normally:35 +late:35 +largely:35 +instead:35 +sure:34 +nevertheless:34 +little:34 +carefully:34 +apparently:34 +anyway:34 +tomorrow:33 +previously:33 +mainly:33 +for instance:33 +currently:33 +increasingly:32 +in particular:32 +entirely:32 +as well:32 +possibly:31 +extremely:31 +equally:31 +hard:29 +surely:28 +frequently:28 +ahead:28 +widely:27 +twice:27 +partly:27 +fairly:27 +somewhere:26 +seriously:26 +elsewhere:26 +closely:26 +straight:25 +no doubt:25 +neither:25 +necessarily:25 +before:25 +all right:25 +totally:24 +tonight:24 +properly:24 +etc:24 +meanwhile:23 +hence:23 +effectively:23 +somewhat:22 +similarly:22 +rapidly:22 +at first:22 +under:21 +strongly:21 +moreover:21 +in addition:21 +at last:21 +virtually:20 +unfortunately:20 +that is:20 +somehow:20 +significantly:20 +rarely:20 +perfectly:20 +originally:20 +low:20 +inside:20 +badly:20 +afterwards:20 +quietly:19 +naturally:19 +in general:19 +heavily:19 +gently:19 +firmly:19 +at once:19 +and so on:19 +absolutely:19 +shortly:18 +regularly:18 +occasionally:18 +mostly:18 +initially:18 +deeply:18 +close:18 +abroad:18 +subsequently:17 +specifically:17 +short:17 +gradually:17 +aside:17 +successfully:16 +precisely:16 +outside:16 +once again:16 +greatly:16 +essentially:16 +easy:16 +apart:16 +anywhere:16 +respectively:15 +pretty:15 +open:15 +truly:14 +that:14 +primarily:14 +inevitably:14 +furthermore:14 +fast:14 +constantly:14 +briefly:14 +at present:14 +altogether:14 +across:14 +a bit:14 +ultimately:13 +reasonably:13 +readily:13 +presumably:13 +newly:13 +less than:13 +everywhere:13 +deliberately:13 +considerably:13 +automatically:13 +approximately:13 +thereby:12 +surprisingly:12 +sufficiently:12 +softly:12 +sharply:12 +secondly:12 +deep:12 +commonly:12 +behind:12 +undoubtedly:11 +purely:11 +potentially:11 +personally:11 +once more:11 +notably:11 +near:11 +ill:11 +consequently:11 +closer:11 +barely:11 +accordingly:11 +wide:10 +wholly:10 +up to:10 +typically:10 +since:10 +roughly:10 +nowhere:10 +namely:10 +longer:10 +good:10 +easier:10 +definitely:10 +by now:10 +traditionally:9 +though:9 +thoroughly:9 +strictly:9 +specially:9 +sadly:9 +physically:9 +mentally:9 +lightly:9 +formally:9 +desperately:9 +substantially:8 +steadily:8 +sooner:8 +solely:8 +so as:8 +simultaneously:8 +severely:8 +separately:8 +scarcely:8 +safely:8 +politically:8 +officially:8 +locally:8 +literally:8 +in part:8 +happily:8 +from time to time:8 +formerly:8 +forever:8 +fair:8 +exclusively:8 +direct:8 +correctly:8 +besides:8 +basically:8 +alternatively:8 +upwards:7 +upstairs:7 +thereafter:7 +socially:7 +seldom:7 +reportedly:7 +remarkably:7 +regardless:7 +publicly:7 +past:7 +invariably:7 +instantly:7 +in short:7 +hitherto:7 +freely:7 +fortunately:7 +firstly:7 +evidently:7 +dramatically:7 +downstairs:7 +consistently:7 +by no means:7 +broadly:7 +backwards:7 +all but:7 +actively:7 +utterly:6 +tight:6 +tightly:6 +thick:6 +temporarily:6 +swiftly:6 +seemingly:6 +repeatedly:6 +predominantly:6 +practically:6 +positively:6 +partially:6 +openly:6 +onwards:6 +no:6 +nowadays:6 +nonetheless:6 +independently:6 +importantly:6 +hopefully:6 +genuinely:6 +forwards:6 +forth:6 +for long:6 +for ever:6 +far from:6 +explicitly:6 +even so:6 +differently:6 +continually:6 +as yet:6 +as usual:6 +accurately:6 +worldwide:5 +within:5 +terribly:5 +sincerely:5 +silently:5 +rightly:5 +privately:5 +permanently:5 +overall:5 +neatly:5 +nearby:5 +loudly:5 +likewise:5 +legally:5 +just about:5 +individually:5 +indirectly:5 +in full:5 +in common:5 +ideally:5 +half:5 +faster:5 +efficiently:5 +daily:5 +comparatively:5 +clear:5 +bitterly:5 +beyond:5 +beautifully:5 +annually:5 +angrily:5 +allegedly:5 +alike:5 +adequately:5 +abruptly:5 +wrong:4 +worse:4 +wild:4 +vaguely:4 +urgently:4 +unusually:4 +unexpectedly:4 +under way:4 +technically:4 +supposedly:4 +strangely:4 +smoothly:4 +slow:4 +sideways:4 +sharp:4 +sexually:4 +reluctantly:4 +quick:4 +public:4 +promptly:4 +principally:4 +presently:4 +poorly:4 +plain:4 +per annum:4 +overseas:4 +overnight:4 +on board:4 +jointly:4 +ironically:4 +incidentally:4 +in public:4 +honestly:4 +hastily:4 +harder:4 +fundamentally:4 +fiercely:4 +false:4 +extra:4 +extensively:4 +exceptionally:4 +economically:4 +duly:4 +doubtless:4 +distinctly:4 +curiously:4 +cool:4 +continuously:4 +comfortably:4 +cheap:4 +by far:4 +but:4 +as good as:4 +as a whole:4 +appropriately:4 +aloud:4 +all the same:4 +wrongly:3 +wildly:3 +violently:3 +vigorously:3 +vice versa:3 +upright:3 +underneath:3 +true:3 +thoughtfully:3 +this:3 +thirdly:3 +that is to say:3 +systematically:3 +suitably:3 +sort of:3 +secretly:3 +radically:3 +proudly:3 +progressively:3 +profoundly:3 +preferably:3 +politely:3 +plainly:3 +painfully:3 +over there:3 +oddly:3 +none the less:3 +nicely:3 +nervously:3 +nearer:3 +nationally:3 +narrowly:3 +mutually:3 +mildly:3 +markedly:3 +loud:3 +loose:3 +loosely:3 +like:3 +least:3 +lately:3 +kindly:3 +internationally:3 +interestingly:3 +instinctively:3 +indoors:3 +incredibly:3 +immensely:3 +historically:3 +highest:3 +halfway:3 +gmt:3 +furiously:3 +freshly:3 +frankly:3 +for the time being:3 +for the most part:3 +for once:3 +for good:3 +for certain:3 +flat:3 +finely:3 +financially:3 +faintly:3 +evenly:3 +enormously:3 +emotionally:3 +eagerly:3 +due:3 +downwards:3 +deeper:3 +dead:3 +conversely:3 +conveniently:3 +consciously:3 +commercially:3 +clean:3 +chiefly:3 +centrally:3 +cautiously:3 +casually:3 +calmly:3 +bloody:3 +big:3 +bad:3 +at large:3 +at best:3 +ashore:3 +arguably:3 +any longer:3 +anxiously:3 +admittedly:3 +wonderfully:2 +willingly:2 +wearily:2 +weakly:2 +warmly:2 +voluntarily:2 +vividly:2 +visually:2 +vastly:2 +usefully:2 +unnecessarily:2 +universally:2 +uniquely:2 +unduly:2 +underway:2 +understandably:2 +underground:2 +uncomfortably:2 +unanimously:2 +throughout:2 +thinly:2 +thereof:2 +theoretically:2 +thankfully:2 +stiff:2 +stiffly:2 +steady:2 +statistically:2 +spontaneously:2 +sometime:2 +so much as:2 +small:2 +sensibly:2 +satisfactorily:2 +richly:2 +professionally:2 +powerfully:2 +pleasantly:2 +patiently:2 +parallel:2 +paradoxically:2 +overwhelmingly:2 +overhead:2 +over here:2 +outwards:2 +ostensibly:2 +notoriously:2 +morally:2 +momentarily:2 +moderately:2 +marginally:2 +luckily:2 +logically:2 +live:2 +likely:2 +internally:2 +intently:2 +intensely:2 +inherently:2 +infinitely:2 +indefinitely:2 +in vain:2 +in private:2 +in between:2 +implicitly:2 +impatiently:2 +illegally:2 +hurriedly:2 +hopelessly:2 +higher:2 +helplessly:2 +grimly:2 +great:2 +generously:2 +from now on:2 +frantically:2 +favourably:2 +faithfully:2 +extraordinarily:2 +expressly:2 +environmentally:2 +enthusiastically:2 +en route:2 +drily:2 +decidedly:2 +dangerously:2 +critically:2 +confidently:2 +collectively:2 +coldly:2 +cheerfully:2 +cheaply:2 +characteristically:2 +briskly:2 +brilliantly:2 +brightly:2 +beforehand:2 +bc:2 +at random:2 +at length:2 +as it were:2 +anything but:2 +alongside:2 +additionally:2 +acutely:2 +accidentally:2 +wryly:1 +worst:1 +without:1 +wistfully:1 +wisely:1 +weekly:1 +warily:1 +vitally:1 +visibly:1 +vertically:1 +vehemently:1 +variously:1 +upward:1 +upstream:1 +upside down:1 +up front:1 +unwittingly:1 +unsuccessfully:1 +unreasonably:1 +unquestionably:1 +uniformly:1 +unhappily:1 +unfairly:1 +unequivocally:1 +uneasily:1 +unconsciously:1 +uncertainly:1 +triumphantly:1 +tremendously:1 +tragically:1 +to and fro:1 +time and again:1 +thickly:1 +therein:1 +thence:1 +tentatively:1 +tenderly:1 +sympathetically:1 +sweetly:1 +suspiciously:1 +superficially:1 +superbly:1 +subtly:1 +stubbornly:1 +strikingly:1 +strategically:1 +sternly:1 +steeply:1 +squarely:1 +speedily:1 +solidly:1 +solemnly:1 +smartly:1 +slower:1 +skilfully:1 +selectively:1 +securely:1 +scientifically:1 +scarce:1 +savagely:1 +safer:1 +ruthlessly:1 +ruefully:1 +routinely:1 +rigorously:1 +rigidly:1 +resolutely:1 +remotely:1 +reliably:1 +relentlessly:1 +regrettably:1 +real:1 +realistically:1 +ready:1 +rationally:1 +randomly:1 +quicker:1 +psychologically:1 +prominently:1 +prior:1 +prematurely:1 +predictably:1 +popularly:1 +pointedly:1 +persistently:1 +periodically:1 +per se:1 +per capita:1 +peculiarly:1 +peacefully:1 +patently:1 +passionately:1 +part-time:1 +overtly:1 +outright:1 +ordinarily:1 +once and for all:1 +ok:1 +okay:1 +offshore:1 +objectively:1 +not:1 +noticeably:1 +northwards:1 +nominally:1 +noisily:1 +negatively:1 +nationwide:1 +mysteriously:1 +monthly:1 +modestly:1 +mistakenly:1 +miserably:1 +miraculously:1 +mercifully:1 +mechanically:1 +materially:1 +massively:1 +manually:1 +lower:1 +lovingly:1 +legitimately:1 +left:1 +lazily:1 +latterly:1 +lastly:1 +knowingly:1 +kind of:1 +keenly:1 +justly:1 +justifiably:1 +jolly:1 +irritably:1 +inwards:1 +inwardly:1 +involuntarily:1 +intrinsically:1 +intimately:1 +intermittently:1 +inter alia:1 +intentionally:1 +intellectually:1 +innocently:1 +inland:1 +infrequently:1 +informally:1 +inextricably:1 +inexorably:1 +indignantly:1 +incorrectly:1 +inadvertently:1 +in vitro:1 +in the main:1 +in situ:1 +in brief:1 +idly:1 +ibid:1 +ibid.:1 +huskily:1 +hugely:1 +hotly:1 +horribly:1 +horizontally:1 +hesitantly:1 +hereby:1 +henceforth:1 +harshly:1 +half-way:1 +half way:1 +habitually:1 +grudgingly:1 +grossly:1 +gravely:1 +gratefully:1 +graphically:1 +gracefully:1 +gloomily:1 +gladly:1 +gingerly:1 +geographically:1 +genetically:1 +full:1 +full-time:1 +forthwith:1 +forcibly:1 +forcefully:1 +for sure:1 +flatly:1 +fine:1 +farther:1 +falsely:1 +externally:1 +expertly:1 +experimentally:1 +excitedly:1 +excessively:1 +exceedingly:1 +ever so:1 +endlessly:1 +emphatically:1 +eminently:1 +elegantly:1 +electronically:1 +effortlessly:1 +eastwards:1 +earnestly:1 +drastically:1 +downstream:1 +downhill:1 +doubtfully:1 +doubly:1 +double:1 +disproportionately:1 +discreetly:1 +dimly:1 +densely:1 +delicately:1 +defiantly:1 +defensively:1 +decisively:1 +dearly:1 +darkly:1 +culturally:1 +cruelly:1 +crudely:1 +crucially:1 +crossly:1 +correspondingly:1 +coolly:1 +convincingly:1 +conventionally:1 +conspicuously:1 +conceivably:1 +closest:1 +clinically:1 +cleverly:1 +circa:1 +chemically:1 +carelessly:1 +by and large:1 +busily:1 +brutally:1 +bravely:1 +boldly:1 +bluntly:1 +blindly:1 +blankly:1 +beneath:1 +belatedly:1 +awkwardly:1 +awfully:1 +at worst:1 +at most:1 +astonishingly:1 +as a matter of fact:1 +artificially:1 +arbitrarily:1 +approx:1 +apologetically:1 +anymore:1 +anyhow:1 +and so forth:1 +amazingly:1 +alternately:1 +aloft:1 +all of a sudden:1 +alarmingly:1 +aggressively:1 +afield:1 +affectionately:1 +adversely:1 +admirably:1 +absently:1 +aboard:1 +aback:1 +a.d.:1 +a lot:1 diff --git a/tpcdsgen/data/articles.dst b/tpcdsgen/data/articles.dst new file mode 100644 index 00000000..d7d3fa80 --- /dev/null +++ b/tpcdsgen/data/articles.dst @@ -0,0 +1,11 @@ +-- articles +-- from the common word list +-- values weights +-- ======= ======= +-- 1. article 1. weight +------ +the:163 +a:504 +an:9 +no:3 +every:1 diff --git a/tpcdsgen/data/auxiliaries.dst b/tpcdsgen/data/auxiliaries.dst new file mode 100644 index 00000000..81b26d56 --- /dev/null +++ b/tpcdsgen/data/auxiliaries.dst @@ -0,0 +1,25 @@ +-- auxiliary.dst sentence forms +-- values weights +-- ====== ======= +-- 1. auxialary 1. weight +--------- +will: 2 +will not: 1 +shall: 2 +shall not: 1 +may: 2 +may not: 1 +might: 2 +might not: 1 +can: 2 +cannot: 1 +could: 2 +could not: 1 +must: 2 +must not: 1 +ought to: 2 +used to: 2 +should: 2 +should not: 1 +would: 2 +would not: 1 diff --git a/tpcdsgen/data/book_class.dst b/tpcdsgen/data/book_class.dst new file mode 100644 index 00000000..2b2ff548 --- /dev/null +++ b/tpcdsgen/data/book_class.dst @@ -0,0 +1,24 @@ +------ +-- book_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +arts, 12: 1 +business, 12: 1 +computers, 12: 1 +entertainments, 12: 1 +history, 12: 1 +parenting, 12: 1 +reference, 12: 1 +romance, 12: 1 +science, 12: 1 +travel, 12: 1 +cooking, 12: 1 +home repair, 12: 1 +self-help, 12: 1 +sports, 12: 1 +fiction, 12: 1 +mystery, 12: 1 diff --git a/tpcdsgen/data/brand_syllables.dst b/tpcdsgen/data/brand_syllables.dst new file mode 100644 index 00000000..91e09f76 --- /dev/null +++ b/tpcdsgen/data/brand_syllables.dst @@ -0,0 +1,16 @@ +------ +-- brand_syllables +-- values weights +-- -------------------------- +-- 1. syllable 1. uniform +------ +univ: 1 +amalg: 1 +importo:1 +exporti:1 +edu pack:1 +scholar:1 +corp:1 +brand:1 +nameless:1 +maxi:1 diff --git a/tpcdsgen/data/buy_potential.dst b/tpcdsgen/data/buy_potential.dst new file mode 100644 index 00000000..f5c95aa6 --- /dev/null +++ b/tpcdsgen/data/buy_potential.dst @@ -0,0 +1,13 @@ +------ +-- buy_potential +------ +-- values weights +-- ======= ======= +-- 1. range 1. weight +---------------------- +0-500: 17 +501-1000: 17 +1001-5000: 35 +5001-10000: 22 +>10000: 5 +Unknown: 4 diff --git a/tpcdsgen/data/calendar.dst b/tpcdsgen/data/calendar.dst new file mode 100755 index 00000000..c010b648 --- /dev/null +++ b/tpcdsgen/data/calendar.dst @@ -0,0 +1,421 @@ +-- +-- Legal Notice +-- +-- This document and associated source code (the Work) is a part of a +-- benchmark specification maintained by the TPC. +-- +-- The TPC reserves all right, title, and interest to the Work as provided +-- under U.S. and international laws, including without limitation all patent +-- and trademark rights therein. +-- +-- No Warranty +-- +-- 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION +-- CONTAINED HEREIN IS PROVIDED AS IS AND WITH ALL FAULTS, AND THE +-- AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER +-- WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, +-- INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES, +-- DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR +-- PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF +-- WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE. +-- ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, +-- QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT +-- WITH REGARD TO THE WORK. +-- 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO +-- ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE +-- COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS +-- OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT, +-- INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY, +-- OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT +-- RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD +-- ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. +-- +-- Contributors: +-- Gradient Systems +-- +-- +-- +-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +-- Begin distribution definitions +-- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +------ +-- calendar +-- value weight +-- ===== ====== +-- 1. day number (int) 1. uniform, non-leap year +-- 2. month name (varchar) 2. uniform, leap year +-- 3. day of month (int) 3. sales likelihood (skewed) +-- 4. season (varchar) 4. sales likelihood (leap year) +-- 5. month number (int) 5. returns likelihood (skewed) +-- 6. quarter (int) 6. returns likelihood(leap year) +-- 7. first of month (int) 7. combined skew (low+medium_high) +-- 8. is_holiday (int) 8. low +-- 9. 9. medium +-- 10. 10. high +------ +1, January, 1, Winter, 1, 1, 1, 1:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +2, January, 2, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +3, January, 3, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +4, January, 4, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +5, January, 5, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +6, January, 6, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +7, January, 7, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +8, January, 8, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +9, January, 9, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +10, January, 10, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +11, January, 11, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +12, January, 12, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +13, January, 13, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +14, January, 14, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +15, January, 15, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +16, January, 16, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +17, January, 17, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +18, January, 18, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +19, January, 19, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +20, January, 20, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +21, January, 21, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +22, January, 22, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +23, January, 23, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +24, January, 24, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +25, January, 25, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +26, January, 26, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +27, January, 27, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +28, January, 28, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +29, January, 29, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +30, January, 30, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +31, January, 31, Winter, 1, 1, 1, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +32, February, 1, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +33, February, 2, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +34, February, 3, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +35, February, 4, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +36, February, 5, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +37, February, 6, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +38, February, 7, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +39, February, 8, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +40, February, 9, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +41, February, 10, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +42, February, 11, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +43, February, 12, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +44, February, 13, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +45, February, 14, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +46, February, 15, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +47, February, 16, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +48, February, 17, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +49, February, 18, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +50, February, 19, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +51, February, 20, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +52, February, 21, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +53, February, 22, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +54, February, 23, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +55, February, 24, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +56, February, 25, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +57, February, 26, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +58, February, 27, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +59, February, 28, Winter, 2, 1, 32, 0:1, 1, 29, 29, 100, 100, 29, 1, 0, 0 +60, February, 29, Winter, 2, 1, 32, 0:0, 1, 0, 29, 0, 100, 0, 0, 0, 0 +61, March, 1, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +62, March, 2, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +63, March, 3, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +64, March, 4, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +65, March, 5, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +66, March, 6, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +67, March, 7, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +68, March, 8, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +69, March, 9, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +70, March, 10, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +71, March, 11, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +72, March, 12, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +73, March, 13, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +74, March, 14, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +75, March, 15, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +76, March, 16, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +77, March, 17, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +78, March, 18, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +79, March, 19, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +80, March, 20, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +81, March, 21, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +82, March, 22, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +83, March, 23, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +84, March, 24, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +85, March, 25, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +86, March, 26, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +87, March, 27, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +88, March, 28, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +89, March, 29, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +90, March, 30, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +91, March, 31, Spring, 3, 1, 61, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +92, April, 1, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +93, April, 2, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +94, April, 3, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +95, April, 4, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +96, April, 5, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +97, April, 6, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +98, April, 7, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +99, April, 8, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +100, April, 9, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +101, April, 10, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +102, April, 11, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +103, April, 12, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +104, April, 13, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +105, April, 14, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +106, April, 15, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +107, April, 16, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +108, April, 17, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +109, April, 18, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +110, April, 19, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +111, April, 20, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +112, April, 21, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +113, April, 22, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +114, April, 23, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +115, April, 24, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +116, April, 25, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +117, April, 26, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +118, April, 27, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +119, April, 28, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +120, April, 29, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +121, April, 30, Spring, 4, 2, 92, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +122, May, 1, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +123, May, 2, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +124, May, 3, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +125, May, 4, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +126, May, 5, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +127, May, 6, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +128, May, 7, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +129, May, 8, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +130, May, 9, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +131, May, 10, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +132, May, 11, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +133, May, 12, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +134, May, 13, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +135, May, 14, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +136, May, 15, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +137, May, 16, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +138, May, 17, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +139, May, 18, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +140, May, 19, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +141, May, 20, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +142, May, 21, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +143, May, 22, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +144, May, 23, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +145, May, 24, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +146, May, 25, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +147, May, 26, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +148, May, 27, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +149, May, 28, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +150, May, 29, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +151, May, 30, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +152, May, 31, Spring, 5, 2, 122, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +153, June, 1, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +154, June, 2, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +155, June, 3, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +156, June, 4, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +157, June, 5, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +158, June, 6, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +159, June, 7, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +160, June, 8, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +161, June, 9, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +162, June, 10, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +163, June, 11, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +164, June, 12, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +165, June, 13, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +166, June, 14, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +167, June, 15, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +168, June, 16, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +169, June, 17, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +170, June, 18, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +171, June, 19, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +172, June, 20, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +173, June, 21, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +174, June, 22, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +175, June, 23, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +176, June, 24, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +177, June, 25, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +178, June, 26, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +179, June, 27, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +180, June, 28, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +181, June, 29, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +182, June, 30, Summer, 6, 2, 153, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +183, July, 1, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +184, July, 2, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +185, July, 3, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +186, July, 4, Summer, 7, 3, 183, 1:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +187, July, 5, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +188, July, 6, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +189, July, 7, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +190, July, 8, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +191, July, 9, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +192, July, 10, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +193, July, 11, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +194, July, 12, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +195, July, 13, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +196, July, 14, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +197, July, 15, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +198, July, 16, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +199, July, 17, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +200, July, 18, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +201, July, 19, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +202, July, 20, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +203, July, 21, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +204, July, 22, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +205, July, 23, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +206, July, 24, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +207, July, 25, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +208, July, 26, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +209, July, 27, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +210, July, 28, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +211, July, 29, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +212, July, 30, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +213, July, 31, Summer, 7, 3, 183, 0:1, 1, 29, 29, 29, 29, 29, 1, 0, 0 +214, August, 1, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +215, August, 2, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +216, August, 3, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +217, August, 4, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +218, August, 5, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +219, August, 6, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +220, August, 7, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +221, August, 8, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +222, August, 9, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +223, August, 10, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +224, August, 11, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +225, August, 12, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +226, August, 13, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +227, August, 14, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +228, August, 15, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +229, August, 16, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +230, August, 17, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +231, August, 18, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +232, August, 19, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +233, August, 20, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +234, August, 21, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +235, August, 22, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +236, August, 23, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +237, August, 24, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +238, August, 25, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +239, August, 26, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +240, August, 27, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +241, August, 28, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +242, August, 29, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +243, August, 30, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +244, August, 31, Summer, 8, 3, 214, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +245, September, 1, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +246, September, 2, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +247, September, 3, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +248, September, 4, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +249, September, 5, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +250, September, 6, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +251, September, 7, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +252, September, 8, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +253, September, 9, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +254, September, 10, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +255, September, 11, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +256, September, 12, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +257, September, 13, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +258, September, 14, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +259, September, 15, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +260, September, 16, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +261, September, 17, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +262, September, 18, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +263, September, 19, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +264, September, 20, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +265, September, 21, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +266, September, 22, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +267, September, 23, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +268, September, 24, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +269, September, 25, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +270, September, 26, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +271, September, 27, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +272, September, 28, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +273, September, 29, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +274, September, 30, Fall, 9, 3, 245, 0:1, 1, 66, 66, 29, 29, 66, 0, 1, 0 +275, October, 1, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +276, October, 2, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +277, October, 3, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +278, October, 4, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +279, October, 5, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +280, October, 6, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +281, October, 7, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +282, October, 8, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +283, October, 9, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +284, October, 10, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +285, October, 11, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +286, October, 12, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +287, October, 13, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +288, October, 14, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +289, October, 15, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +290, October, 16, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +291, October, 17, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +292, October, 18, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +293, October, 19, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +294, October, 20, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +295, October, 21, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +296, October, 22, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +297, October, 23, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +298, October, 24, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +299, October, 25, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +300, October, 26, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +301, October, 27, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +302, October, 28, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +303, October, 29, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +304, October, 30, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +305, October, 31, Fall, 10, 4, 275, 0: 1, 1, 66, 66, 66, 66, 66, 0, 1, 0 +306, November, 1, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +307, November, 2, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +308, November, 3, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +309, November, 4, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +310, November, 5, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +311, November, 6, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +312, November, 7, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +313, November, 8, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +314, November, 9, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +315, November, 10, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +316, November, 11, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +317, November, 12, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +318, November, 13, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +319, November, 14, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +320, November, 15, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +321, November, 16, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +322, November, 17, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +323, November, 18, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +324, November, 19, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +325, November, 20, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +326, November, 21, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +327, November, 22, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +328, November, 23, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +329, November, 24, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +330, November, 25, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +331, November, 26, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +332, November, 27, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +333, November, 28, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +334, November, 29, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +335, November, 30, Fall, 11, 4, 306, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +336, December, 1, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +337, December, 2, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +338, December, 3, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +339, December, 4, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +340, December, 5, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +341, December, 6, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +342, December, 7, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +343, December, 8, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +344, December, 9, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +345, December, 10, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +346, December, 11, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +347, December, 12, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +348, December, 13, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +349, December, 14, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +350, December, 15, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +351, December, 16, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +352, December, 17, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +353, December, 18, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +354, December, 19, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +355, December, 20, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +356, December, 21, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +357, December, 22, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +358, December, 23, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +359, December, 24, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +360, December, 25, Winter, 12, 4, 336, 1: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +361, December, 26, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +362, December, 27, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +363, December, 28, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +364, December, 29, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +365, December, 30, Winter, 12, 4, 336, 0: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 +366, December, 31, Winter, 12, 4, 336, 1: 1, 1, 100, 100, 66, 66, 100, 0, 0, 1 diff --git a/tpcdsgen/data/call_center_classes.dst b/tpcdsgen/data/call_center_classes.dst new file mode 100644 index 00000000..b57298bb --- /dev/null +++ b/tpcdsgen/data/call_center_classes.dst @@ -0,0 +1,9 @@ +------ +-- call_center_class +-- values weights +-- ----------------------- +-- 1. class 1. uniform +------ +small: 1 +medium: 1 +large: 1 diff --git a/tpcdsgen/data/call_center_hours.dst b/tpcdsgen/data/call_center_hours.dst new file mode 100644 index 00000000..d3fe9b3f --- /dev/null +++ b/tpcdsgen/data/call_center_hours.dst @@ -0,0 +1,9 @@ +------ +-- call_center_hours +-- values weights +-- ======= ========== +-- 1. hours 1. weighted +------ +8AM-4PM: 10 +8AM-12AM: 2 +8AM-8AM: 1 diff --git a/tpcdsgen/data/call_centers.dst b/tpcdsgen/data/call_centers.dst new file mode 100644 index 00000000..7b1b8276 --- /dev/null +++ b/tpcdsgen/data/call_centers.dst @@ -0,0 +1,19 @@ +------ +-- call_centers +-- values weights +-- ====== ======== +-- 1. name : varchar 1. uniform +-- 2. sales percentage +------ +New England: 1, 8 +NY Metro: 1, 16 +Mid Atlantic: 1, 10 +Southeastern: 1, 8 +North Midwest: 1, 6 +Central Midwest: 1, 7 +South Midwest: 1, 8 +Pacific Northwest: 1, 9 +California: 1, 17 +Southwest: 1, 7 +Hawaii/Alaska: 1, 3 +Other: 1, 1 diff --git a/tpcdsgen/data/catalog_page_types.dst b/tpcdsgen/data/catalog_page_types.dst new file mode 100644 index 00000000..fd736aa1 --- /dev/null +++ b/tpcdsgen/data/catalog_page_types.dst @@ -0,0 +1,10 @@ +------ +-- catalog_page_type +-- values weights +-- ----------------------- +-- 1. content type 1. distribution freq +-- 2. sales volume +------ +bi-annual: 1, 50 +quarterly: 4, 25 +monthly: 12, 25 diff --git a/tpcdsgen/data/categories.dst b/tpcdsgen/data/categories.dst new file mode 100644 index 00000000..743c6ac0 --- /dev/null +++ b/tpcdsgen/data/categories.dst @@ -0,0 +1,18 @@ +------ +-- categories +-- values weights +-- ----------------------- +-- 1. name 1. uniform +-- 2. class dist name +-- 3. has sizes? +------ +Women, women_class, 1: 1 +Men, men_class, 1: 1 +Children, children_class, 1: 1 +Shoes, shoe_class, 1: 1 +Music, music_class, 0: 1 +Jewelry, jewelry_class, 0: 1 +Home, home_class, 0: 1 +Sports, sport_class, 0: 1 +Books, book_class, 0: 1 +Electronics, electronic_class, 0: 1 diff --git a/tpcdsgen/data/children_class.dst b/tpcdsgen/data/children_class.dst new file mode 100644 index 00000000..87b0b609 --- /dev/null +++ b/tpcdsgen/data/children_class.dst @@ -0,0 +1,12 @@ +------ +-- children_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +newborn, 2: 1 +infants, 2: 1 +toddlers, 2: 1 +school-uniforms, 2: 1 diff --git a/tpcdsgen/data/cities.dst b/tpcdsgen/data/cities.dst new file mode 100755 index 00000000..abf85810 --- /dev/null +++ b/tpcdsgen/data/cities.dst @@ -0,0 +1,1053 @@ +-- +-- Legal Notice +-- +-- This document and associated source code (the Work) is a part of a +-- benchmark specification maintained by the TPC. +-- +-- The TPC reserves all right, title, and interest to the Work as provided +-- under U.S. and international laws, including without limitation all patent +-- and trademark rights therein. +-- +-- No Warranty +-- +-- 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION +-- CONTAINED HEREIN IS PROVIDED AS IS AND WITH ALL FAULTS, AND THE +-- AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER +-- WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, +-- INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES, +-- DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR +-- PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF +-- WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE. +-- ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, +-- QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT +-- WITH REGARD TO THE WORK. +-- 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO +-- ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE +-- COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS +-- OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT, +-- INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY, +-- OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT +-- RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD +-- ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. +-- +-- Contributors: +-- Gradient Systems +-- +-- +-- +-- +-- +------ +-- cities.dst +-- the 1000 most common place names, from the USGS list of populated places +-- second level used to populate the item hierarchy +-- values weights +-- ====== ========= +-- 1. city name 1. skewed from USGS (usgs) +-- 2. uniform (uniform) +-- 3. large cities (large) +-- 4. medium step (medium) +-- 5. small cities (small) +-- 6. unified step function +------ +Midway:212, 1, 1, 0, 0, 600 +Fairview:199, 1, 1, 0, 0, 600 +Oak Grove:160, 1, 1, 0, 0, 600 +Five Points:147, 1, 1, 0, 0, 600 +Pleasant Hill:119, 1, 1, 0, 0, 600 +Riverside:117, 1, 1, 0, 0, 600 +Mount Pleasant:116, 1, 1, 0, 0, 600 +Centerville:108, 1, 1, 0, 0, 600 +Bethel:108, 1, 1, 0, 0, 600 +New Hope:105, 1, 1, 0, 0, 600 +Liberty:98, 1, 1, 0, 0, 600 +Union:94, 1, 1, 0, 0, 600 +Pleasant Valley:90, 1, 1, 0, 0, 600 +Oakland:87, 1, 1, 0, 0, 600 +Salem:86, 1, 1, 0, 0, 600 +Pleasant Grove:86, 1, 1, 0, 0, 600 +Greenwood:85, 1, 1, 0, 0, 600 +Shady Grove:84, 1, 1, 0, 0, 600 +Pine Grove:82, 1, 1, 0, 0, 600 +Shiloh:78, 1, 1, 0, 0, 600 +Oak Hill:77, 1, 1, 0, 0, 600 +Concord:74, 1, 1, 0, 0, 600 +Georgetown:73, 1, 1, 0, 0, 600 +Cedar Grove:73, 1, 1, 0, 0, 600 +Lakeview:72, 1, 1, 0, 0, 600 +Antioch:71, 1, 1, 0, 0, 600 +Glendale:70, 1, 1, 0, 0, 600 +Hopewell:67, 1, 1, 0, 0, 600 +Friendship:67, 1, 1, 0, 0, 600 +Sunnyside:66, 1, 1, 0, 0, 600 +Spring Hill:65, 1, 1, 0, 0, 600 +Lakewood:65, 1, 1, 0, 0, 600 +Springfield:64, 1, 1, 0, 0, 600 +Stringtown:62, 1, 1, 0, 0, 600 +Harmony:62, 1, 1, 0, 0, 600 +Riverview:60, 1, 1, 0, 0, 600 +Buena Vista:60, 1, 1, 0, 0, 600 +Highland:59, 1, 1, 0, 0, 600 +Highland Park:58, 1, 1, 0, 0, 600 +Franklin:58, 1, 1, 0, 0, 600 +Woodlawn:56, 1, 1, 0, 0, 600 +Mount Vernon:56, 1, 1, 0, 0, 600 +Lakeside:56, 1, 1, 0, 0, 600 +Glenwood:55, 1, 1, 0, 0, 600 +Fairfield:55, 1, 1, 0, 0, 600 +Oakdale:54, 1, 1, 0, 0, 600 +Spring Valley:53, 1, 1, 0, 0, 600 +Walnut Grove:52, 1, 1, 0, 0, 600 +Providence:52, 1, 1, 0, 0, 600 +Mount Zion:52, 1, 1, 0, 0, 600 +Greenville:52, 1, 1, 0, 0, 600 +Mount Olive:51, 1, 1, 0, 0, 600 +Wildwood:50, 1, 1, 0, 0, 600 +Hillcrest:50, 1, 1, 0, 0, 600 +Crossroads:50, 1, 1, 0, 0, 600 +Belmont:50, 1, 1, 0, 0, 600 +Wilson:49, 1, 1, 0, 0, 600 +Riverdale:49, 1, 1, 0, 0, 600 +Newport:49, 1, 1, 0, 0, 600 +Springdale:48, 1, 1, 0, 0, 600 +Mountain View:48, 1, 1, 0, 0, 600 +Forest Hills:48, 1, 1, 0, 0, 600 +Bridgeport:48, 1, 1, 0, 0, 600 +White Oak:47, 1, 1, 0, 0, 600 +Oakwood:47, 1, 1, 0, 0, 600 +Newtown:47, 1, 1, 0, 0, 600 +Macedonia:47, 1, 1, 0, 0, 600 +Five Forks:47, 1, 1, 0, 0, 600 +Edgewood:47, 1, 1, 0, 0, 600 +Arlington:47, 1, 1, 0, 0, 600 +Unionville:46, 1, 1, 0, 0, 600 +Red Hill:46, 1, 1, 0, 0, 600 +Clinton:46, 1, 1, 0, 0, 600 +Woodville:45, 1, 1, 0, 0, 600 +Summit:45, 1, 1, 0, 0, 600 +Jamestown:45, 1, 1, 0, 0, 600 +Hamilton:45, 1, 1, 0, 0, 600 +Clifton:45, 1, 1, 0, 0, 600 +Union Hill:44, 1, 1, 0, 0, 600 +Sulphur Springs:44, 1, 1, 0, 0, 600 +Lincoln:44, 1, 1, 0, 0, 600 +Lebanon:44, 1, 1, 0, 0, 600 +Farmington:44, 1, 1, 0, 0, 600 +Enterprise:43, 1, 1, 0, 0, 600 +Brownsville:43, 1, 1, 0, 0, 600 +Ashland:43, 1, 1, 0, 0, 600 +Woodland:42, 1, 1, 0, 0, 600 +Waterloo:42, 1, 1, 0, 0, 600 +Valley View:42, 1, 1, 0, 0, 600 +Oak Ridge:42, 1, 1, 0, 0, 600 +Maple Grove:42, 1, 1, 0, 0, 600 +Kingston:42, 1, 1, 0, 0, 600 +Jackson:42, 1, 1, 0, 0, 600 +Greenfield:42, 1, 1, 0, 0, 600 +Green Acres:42, 1, 1, 0, 0, 600 +Plainview:41, 1, 1, 0, 0, 600 +Marion:41, 1, 1, 0, 0, 600 +Florence:41, 1, 1, 0, 0, 600 +Deerfield:41, 1, 1, 0, 0, 600 +Bunker Hill:41, 1, 1, 0, 0, 600 +Smithville:40, 1, 0, 0, 1, 1 +Rockville:40, 1, 0, 0, 1, 1 +Melrose:40, 1, 0, 0, 1, 1 +Magnolia:40, 1, 0, 0, 1, 1 +Jefferson:40, 1, 0, 0, 1, 1 +Jacksonville:40, 1, 0, 0, 1, 1 +Hebron:40, 1, 0, 0, 1, 1 +Avondale:40, 1, 0, 0, 1, 1 +Petersburg:39, 1, 0, 0, 1, 1 +Needmore:39, 1, 0, 0, 1, 1 +Mount Carmel:39, 1, 0, 0, 1, 1 +Eureka:39, 1, 0, 0, 1, 1 +Eden:39, 1, 0, 0, 1, 1 +Dover:39, 1, 0, 0, 1, 1 +Corinth:39, 1, 0, 0, 1, 1 +Buffalo:39, 1, 0, 0, 1, 1 +Rosedale:38, 1, 0, 0, 1, 1 +Pleasant View:38, 1, 0, 0, 1, 1 +Milton:38, 1, 0, 0, 1, 1 +Independence:38, 1, 0, 0, 1, 1 +Grandview:38, 1, 0, 0, 1, 1 +Four Corners:38, 1, 0, 0, 1, 1 +Cloverdale:38, 1, 0, 0, 1, 1 +Westwood:37, 1, 0, 0, 1, 1 +Vernon:37, 1, 0, 0, 1, 1 +Sharon:37, 1, 0, 0, 1, 1 +Richland:37, 1, 0, 0, 1, 1 +Beulah:37, 1, 0, 0, 1, 1 +Williamsburg:36, 1, 0, 0, 1, 1 +Hollywood:36, 1, 0, 0, 1, 1 +Hillsdale:36, 1, 0, 0, 1, 1 +Ebenezer:36, 1, 0, 0, 1, 1 +Cross Roads:36, 1, 0, 0, 1, 1 +Winchester:35, 1, 0, 0, 1, 1 +Washington:35, 1, 0, 0, 1, 1 +Troy:35, 1, 0, 0, 1, 1 +Prospect:35, 1, 0, 0, 1, 1 +Oxford:35, 1, 0, 0, 1, 1 +Norwood:35, 1, 0, 0, 1, 1 +Mill Creek:35, 1, 0, 0, 1, 1 +Middletown:35, 1, 0, 0, 1, 1 +Jericho:35, 1, 0, 0, 1, 1 +Forest Hills:35, 1, 0, 0, 1, 1 +Columbia:35, 1, 0, 0, 1, 1 +Bethlehem:35, 1, 0, 0, 1, 1 +Anderson:35, 1, 0, 0, 1, 1 +Waverly:34, 1, 0, 0, 1, 1 +Watson:34, 1, 0, 0, 1, 1 +Rose Hill:34, 1, 0, 0, 1, 1 +Pine Hill:34, 1, 0, 0, 1, 1 +Monroe:34, 1, 0, 0, 1, 1 +Milltown:34, 1, 0, 0, 1, 1 +Manchester:34, 1, 0, 0, 1, 1 +Hilltop:34, 1, 0, 0, 1, 1 +Harrisburg:34, 1, 0, 0, 1, 1 +Dixie:34, 1, 0, 0, 1, 1 +Bellevue:34, 1, 0, 0, 1, 1 +Smyrna:33, 1, 0, 0, 1, 1 +Sherwood Forest:33, 1, 0, 0, 1, 1 +Pleasant Ridge:33, 1, 0, 0, 1, 1 +Forest Park:33, 1, 0, 0, 1, 1 +Dayton:33, 1, 0, 0, 1, 1 +Clayton:33, 1, 0, 0, 1, 1 +Chester:33, 1, 0, 0, 1, 1 +Webster:32, 1, 0, 0, 1, 1 +Warren:32, 1, 0, 0, 1, 1 +Somerset:32, 1, 0, 0, 1, 1 +Richmond:32, 1, 0, 0, 1, 1 +Milford:32, 1, 0, 0, 1, 1 +Johnson:32, 1, 0, 0, 1, 1 +Evergreen:32, 1, 0, 0, 1, 1 +Elmwood:32, 1, 0, 0, 1, 1 +Bloomfield:32, 1, 0, 0, 1, 1 +Bethany:32, 1, 0, 0, 1, 1 +Auburn:32, 1, 0, 0, 1, 1 +Williams:31, 1, 0, 0, 1, 1 +Walker:31, 1, 0, 0, 1, 1 +Rosemont:31, 1, 0, 0, 1, 1 +Millville:31, 1, 0, 0, 1, 1 +Mechanicsville:31, 1, 0, 0, 1, 1 +Liberty Hill:31, 1, 0, 0, 1, 1 +Huntington:31, 1, 0, 0, 1, 1 +Hillsboro:31, 1, 0, 0, 1, 1 +Hickory Grove:31, 1, 0, 0, 1, 1 +Germantown:31, 1, 0, 0, 1, 1 +Flat Rock:31, 1, 0, 0, 1, 1 +Douglas:31, 1, 0, 0, 1, 1 +Cleveland:31, 1, 0, 0, 1, 1 +Central:31, 1, 0, 0, 1, 1 +Brooklyn:31, 1, 0, 0, 1, 1 +Brighton:31, 1, 0, 0, 1, 1 +Alpine:31, 1, 0, 0, 1, 1 +Weston:30, 1, 0, 0, 1, 1 +Taylor:30, 1, 0, 0, 1, 1 +Sherwood Forest:30, 1, 0, 0, 1, 1 +Rockdale:30, 1, 0, 0, 1, 1 +Princeton:30, 1, 0, 0, 1, 1 +Preston:30, 1, 0, 0, 1, 1 +Nelson:30, 1, 0, 0, 1, 1 +Martin:30, 1, 0, 0, 1, 1 +Madison:30, 1, 0, 0, 1, 1 +Logan:30, 1, 0, 0, 1, 1 +Linwood:30, 1, 0, 0, 1, 1 +Hope:30, 1, 0, 0, 1, 1 +Holland:30, 1, 0, 0, 1, 1 +High Point:30, 1, 0, 0, 1, 1 +Goshen:30, 1, 0, 0, 1, 1 +Danville:30, 1, 0, 0, 1, 1 +Burlington:30, 1, 0, 0, 1, 1 +Wallace:29, 1, 0, 0, 1, 1 +Rock Creek:29, 1, 0, 0, 1, 1 +Oak Park:29, 1, 0, 0, 1, 1 +Meadowbrook:29, 1, 0, 0, 1, 1 +Locust Grove:29, 1, 0, 0, 1, 1 +Lexington:29, 1, 0, 0, 1, 1 +Howard:29, 1, 0, 0, 1, 1 +Henderson:29, 1, 0, 0, 1, 1 +Hanover:29, 1, 0, 0, 1, 1 +Hampton:29, 1, 0, 0, 1, 1 +Hamburg:29, 1, 0, 0, 1, 1 +Five Corners:29, 1, 0, 0, 1, 1 +Fairmount:29, 1, 0, 0, 1, 1 +Chapel Hill:29, 1, 0, 0, 1, 1 +Canton:29, 1, 0, 0, 1, 1 +Campbell:29, 1, 0, 0, 1, 1 +Berlin:29, 1, 0, 0, 1, 1 +Baker:29, 1, 0, 0, 1, 1 +Avon:29, 1, 0, 0, 1, 1 +Arcadia:29, 1, 0, 0, 1, 1 +Windsor:28, 1, 0, 0, 1, 1 +Westfield:28, 1, 0, 0, 1, 1 +Sardis:28, 1, 0, 0, 1, 1 +Russellville:28, 1, 0, 0, 1, 1 +Rogers:28, 1, 0, 0, 1, 1 +Rock Springs:28, 1, 0, 0, 1, 1 +Riverton:28, 1, 0, 0, 1, 1 +Randolph:28, 1, 0, 0, 1, 1 +Plymouth:28, 1, 0, 0, 1, 1 +Piney Grove:28, 1, 0, 0, 1, 1 +Pinehurst:28, 1, 0, 0, 1, 1 +Mount Hope:28, 1, 0, 0, 1, 1 +Montrose:28, 1, 0, 0, 1, 1 +Miller:28, 1, 0, 0, 1, 1 +Marshall:28, 1, 0, 0, 1, 1 +Lowell:28, 1, 0, 0, 1, 1 +Keystone:28, 1, 0, 0, 1, 1 +Indian Hills:28, 1, 0, 0, 1, 1 +Hudson:28, 1, 0, 0, 1, 1 +Geneva:28, 1, 0, 0, 1, 1 +Englewood:28, 1, 0, 0, 1, 1 +Cottonwood:28, 1, 0, 0, 1, 1 +Clyde:28, 1, 0, 0, 1, 1 +Cedar Hill:28, 1, 0, 0, 1, 1 +Bristol:28, 1, 0, 0, 1, 1 +Beechwood:28, 1, 0, 0, 1, 1 +Aurora:28, 1, 0, 0, 1, 1 +West Point:27, 1, 0, 0, 1, 1 +Trenton:27, 1, 0, 0, 1, 1 +Sunset:27, 1, 0, 0, 1, 1 +Sunrise:27, 1, 0, 0, 1, 1 +Spring Creek:27, 1, 0, 0, 1, 1 +Rolling Hills:27, 1, 0, 0, 1, 1 +Portland:27, 1, 0, 0, 1, 1 +Pine Ridge:27, 1, 0, 0, 1, 1 +Paradise:27, 1, 0, 0, 1, 1 +Monticello:27, 1, 0, 0, 1, 1 +Monterey:27, 1, 0, 0, 1, 1 +Mitchell:27, 1, 0, 0, 1, 1 +Midland:27, 1, 0, 0, 1, 1 +Jordan:27, 1, 0, 0, 1, 1 +Green Valley:27, 1, 0, 0, 1, 1 +Garfield:27, 1, 0, 0, 1, 1 +Garden City:27, 1, 0, 0, 1, 1 +Cold Spring:27, 1, 0, 0, 1, 1 +Clarksville:27, 1, 0, 0, 1, 1 +Cameron:27, 1, 0, 0, 1, 1 +Cambridge:27, 1, 0, 0, 1, 1 +Asbury:27, 1, 0, 0, 1, 1 +Adams:27, 1, 0, 0, 1, 1 +York:26, 1, 0, 0, 1, 1 +White Rock:26, 1, 0, 0, 1, 1 +Wakefield:26, 1, 0, 0, 1, 1 +Victoria:26, 1, 0, 0, 1, 1 +Unity:26, 1, 0, 0, 1, 1 +Twin Lakes:26, 1, 0, 0, 1, 1 +Three Forks:26, 1, 0, 0, 1, 1 +Sycamore:26, 1, 0, 0, 1, 1 +Springville:26, 1, 0, 0, 1, 1 +Russell:26, 1, 0, 0, 1, 1 +Ridgeway:26, 1, 0, 0, 1, 1 +Pisgah:26, 1, 0, 0, 1, 1 +Morgan:26, 1, 0, 0, 1, 1 +Mayfield:26, 1, 0, 0, 1, 1 +Maplewood:26, 1, 0, 0, 1, 1 +Longview:26, 1, 0, 0, 1, 1 +Linden:26, 1, 0, 0, 1, 1 +Lancaster:26, 1, 0, 0, 1, 1 +Harris:26, 1, 0, 0, 1, 1 +Good Hope:26, 1, 0, 0, 1, 1 +Gibson:26, 1, 0, 0, 1, 1 +Fulton:26, 1, 0, 0, 1, 1 +Deer Park:26, 1, 0, 0, 1, 1 +Charleston:26, 1, 0, 0, 1, 1 +Center:26, 1, 0, 0, 1, 1 +Canaan:26, 1, 0, 0, 1, 1 +Boston:26, 1, 0, 0, 1, 1 +Beverly Hills:26, 1, 0, 0, 1, 1 +Benton:26, 1, 0, 0, 1, 1 +Woodstock:25, 1, 0, 0, 1, 1 +White City:25, 1, 0, 0, 1, 1 +Westville:25, 1, 0, 0, 1, 1 +Waterford:25, 1, 0, 0, 1, 1 +Walnut Hill:25, 1, 0, 0, 1, 1 +Verona:25, 1, 0, 0, 1, 1 +Turner:25, 1, 0, 0, 1, 1 +Sterling:25, 1, 0, 0, 1, 1 +Sheridan:25, 1, 0, 0, 1, 1 +Sand Hill:25, 1, 0, 0, 1, 1 +Pumpkin Center:25, 1, 0, 0, 1, 1 +Powell:25, 1, 0, 0, 1, 1 +Poplar Grove:25, 1, 0, 0, 1, 1 +Perry:25, 1, 0, 0, 1, 1 +Parker:25, 1, 0, 0, 1, 1 +Old Town:25, 1, 0, 0, 1, 1 +Oakley:25, 1, 0, 0, 1, 1 +Newton:25, 1, 0, 0, 1, 1 +New Salem:25, 1, 0, 0, 1, 1 +Nashville:25, 1, 0, 0, 1, 1 +Lyons:25, 1, 0, 0, 1, 1 +Lone Star:25, 1, 0, 0, 1, 1 +Klondike:25, 1, 0, 0, 1, 1 +Johnstown:25, 1, 0, 0, 1, 1 +Holly Springs:25, 1, 0, 0, 1, 1 +Harrison:25, 1, 0, 0, 1, 1 +Garland:25, 1, 0, 0, 1, 1 +Foster:25, 1, 0, 0, 1, 1 +Egypt:25, 1, 0, 0, 1, 1 +Davis:25, 1, 0, 0, 1, 1 +Chestnut Hill:25, 1, 0, 0, 1, 1 +Camden:25, 1, 0, 0, 1, 1 +Buckeye:25, 1, 0, 0, 1, 1 +Brookside:25, 1, 0, 0, 1, 1 +Brentwood:25, 1, 0, 0, 1, 1 +Austin:25, 1, 0, 0, 1, 1 +Allen:25, 1, 0, 0, 1, 1 +Woodside:24, 1, 0, 0, 1, 1 +Stony Point:24, 1, 0, 0, 1, 1 +Stanley:24, 1, 0, 0, 1, 1 +Simpson:24, 1, 0, 0, 1, 1 +Silver Lake:24, 1, 0, 0, 1, 1 +Saint Paul:24, 1, 0, 0, 1, 1 +Rome:24, 1, 0, 0, 1, 1 +Rockland:24, 1, 0, 0, 1, 1 +Ridgewood:24, 1, 0, 0, 1, 1 +Raymond:24, 1, 0, 0, 1, 1 +Piedmont:24, 1, 0, 0, 1, 1 +Paris:24, 1, 0, 0, 1, 1 +Palmyra:24, 1, 0, 0, 1, 1 +Orange:24, 1, 0, 0, 1, 1 +Oakville:24, 1, 0, 0, 1, 1 +Montgomery:24, 1, 0, 0, 1, 1 +Lincoln Park:24, 1, 0, 0, 1, 1 +Laurel:24, 1, 0, 0, 1, 1 +Lake City:24, 1, 0, 0, 1, 1 +Kenwood:24, 1, 0, 0, 1, 1 +Jonesville:24, 1, 0, 0, 1, 1 +Huntsville:24, 1, 0, 0, 1, 1 +Hickory Hill:24, 1, 0, 0, 1, 1 +Grant:24, 1, 0, 0, 1, 1 +Fernwood:24, 1, 0, 0, 1, 1 +Enon:24, 1, 0, 0, 1, 1 +Columbus:24, 1, 0, 0, 1, 1 +Carlisle:24, 1, 0, 0, 1, 1 +Brookfield:24, 1, 0, 0, 1, 1 +Bradford:24, 1, 0, 0, 1, 1 +Boyd:24, 1, 0, 0, 1, 1 +Baldwin:24, 1, 0, 0, 1, 1 +Alton:24, 1, 0, 0, 1, 1 +Allendale:24, 1, 0, 0, 1, 1 +Whitehall:23, 1, 0, 0, 1, 1 +Westwood:23, 1, 0, 0, 1, 1 +Wayne:23, 1, 0, 0, 1, 1 +Utica:23, 1, 0, 0, 1, 1 +Union Grove:23, 1, 0, 0, 1, 1 +Thornton:23, 1, 0, 0, 1, 1 +Shirley:23, 1, 0, 0, 1, 1 +Robinson:23, 1, 0, 0, 1, 1 +Patterson:23, 1, 0, 0, 1, 1 +Palestine:23, 1, 0, 0, 1, 1 +Norton:23, 1, 0, 0, 1, 1 +Northwood:23, 1, 0, 0, 1, 1 +New Haven:23, 1, 0, 0, 1, 1 +Moscow:23, 1, 0, 0, 1, 1 +Lakeland:23, 1, 0, 0, 1, 1 +Indian Springs:23, 1, 0, 0, 1, 1 +Hillside:23, 1, 0, 0, 1, 1 +Helena:23, 1, 0, 0, 1, 1 +Harrisville:23, 1, 0, 0, 1, 1 +Green Acres:23, 1, 0, 0, 1, 1 +Fairfax:23, 1, 0, 0, 1, 1 +Ellis:23, 1, 0, 0, 1, 1 +Easton:23, 1, 0, 0, 1, 1 +Durham:23, 1, 0, 0, 1, 1 +Diamond:23, 1, 0, 0, 1, 1 +Dale:23, 1, 0, 0, 1, 1 +Cuba:23, 1, 0, 0, 1, 1 +Cooper:23, 1, 0, 0, 1, 1 +Cold Springs:23, 1, 0, 0, 1, 1 +Cherry Hill:23, 1, 0, 0, 1, 1 +Center Point:23, 1, 0, 0, 1, 1 +Cairo:23, 1, 0, 0, 1, 1 +Butler:23, 1, 0, 0, 1, 1 +Buckhorn:23, 1, 0, 0, 1, 1 +Beech Grove:23, 1, 0, 0, 1, 1 +Augusta:23, 1, 0, 0, 1, 1 +Athens:23, 1, 0, 0, 1, 1 +Albion:23, 1, 0, 0, 1, 1 +Winona:22, 1, 0, 0, 1, 1 +Willard:22, 1, 0, 0, 1, 1 +Westport:22, 1, 0, 0, 1, 1 +West End:22, 1, 0, 0, 1, 1 +Wellington:22, 1, 0, 0, 1, 1 +Ward:22, 1, 0, 0, 1, 1 +Victor:22, 1, 0, 0, 1, 1 +Sunset Beach:22, 1, 0, 0, 1, 1 +Stanton:22, 1, 0, 0, 1, 1 +Spencer:22, 1, 0, 0, 1, 1 +Smithfield:22, 1, 0, 0, 1, 1 +Slabtown:22, 1, 0, 0, 1, 1 +Sidney:22, 1, 0, 0, 1, 1 +Sherwood:22, 1, 0, 0, 1, 1 +Rock Hill:22, 1, 0, 0, 1, 1 +Ramsey:22, 1, 0, 0, 1, 1 +Porter:22, 1, 0, 0, 1, 1 +Poplar Springs:22, 1, 0, 0, 1, 1 +Perryville:22, 1, 0, 0, 1, 1 +Palmer:22, 1, 0, 0, 1, 1 +Newburg:22, 1, 0, 0, 1, 1 +Middleton:22, 1, 0, 0, 1, 1 +Mapleton:22, 1, 0, 0, 1, 1 +Lawrence:22, 1, 0, 0, 1, 1 +Lafayette:22, 1, 0, 0, 1, 1 +Jonestown:22, 1, 0, 0, 1, 1 +Jimtown:22, 1, 0, 0, 1, 1 +Hunter:22, 1, 0, 0, 1, 1 +Houston:22, 1, 0, 0, 1, 1 +Happy Valley:22, 1, 0, 0, 1, 1 +Hammond:22, 1, 0, 0, 1, 1 +Green Hill:22, 1, 0, 0, 1, 1 +Gordon:22, 1, 0, 0, 1, 1 +Glencoe:22, 1, 0, 0, 1, 1 +Elgin:22, 1, 0, 0, 1, 1 +Dundee:22, 1, 0, 0, 1, 1 +Duncan:22, 1, 0, 0, 1, 1 +Delta:22, 1, 0, 0, 1, 1 +Dalton:22, 1, 0, 0, 1, 1 +Crawford:22, 1, 0, 0, 1, 1 +Country Club Estates:22, 1, 0, 0, 1, 1 +Cedar Springs:22, 1, 0, 0, 1, 1 +Briarwood:22, 1, 0, 0, 1, 1 +Bradley:22, 1, 0, 0, 1, 1 +Bloomington:22, 1, 0, 0, 1, 1 +Arnold:22, 1, 0, 0, 1, 1 +Albany:22, 1, 0, 0, 1, 1 +Adamsville:22, 1, 0, 0, 1, 1 +Zion:21, 1, 0, 0, 1, 1 +Westover:21, 1, 0, 0, 1, 1 +Waterville:21, 1, 0, 0, 1, 1 +Upton:21, 1, 0, 0, 1, 1 +Trinity:21, 1, 0, 0, 1, 1 +Thompson:21, 1, 0, 0, 1, 1 +Thomas:21, 1, 0, 0, 1, 1 +Tanglewood:21, 1, 0, 0, 1, 1 +Sunshine:21, 1, 0, 0, 1, 1 +Sugar Grove:21, 1, 0, 0, 1, 1 +Stockton:21, 1, 0, 0, 1, 1 +Snow Hill:21, 1, 0, 0, 1, 1 +Sherman:21, 1, 0, 0, 1, 1 +Scotland:21, 1, 0, 0, 1, 1 +Roseville:21, 1, 0, 0, 1, 1 +Roosevelt:21, 1, 0, 0, 1, 1 +Rockport:21, 1, 0, 0, 1, 1 +Rochester:21, 1, 0, 0, 1, 1 +Plainfield:21, 1, 0, 0, 1, 1 +Osceola:21, 1, 0, 0, 1, 1 +Mulberry:21, 1, 0, 0, 1, 1 +Millwood:21, 1, 0, 0, 1, 1 +McDonald:21, 1, 0, 0, 1, 1 +Maysville:21, 1, 0, 0, 1, 1 +Mason:21, 1, 0, 0, 1, 1 +Marysville:21, 1, 0, 0, 1, 1 +Mansfield:21, 1, 0, 0, 1, 1 +Lodi:21, 1, 0, 0, 1, 1 +Livingston:21, 1, 0, 0, 1, 1 +Kirkwood:21, 1, 0, 0, 1, 1 +Kent:21, 1, 0, 0, 1, 1 +Ingleside:21, 1, 0, 0, 1, 1 +Hyde Park:21, 1, 0, 0, 1, 1 +Homer:21, 1, 0, 0, 1, 1 +Hickory Hills:21, 1, 0, 0, 1, 1 +Hawthorne:21, 1, 0, 0, 1, 1 +Hartford:21, 1, 0, 0, 1, 1 +Fredonia:21, 1, 0, 0, 1, 1 +Evansville:21, 1, 0, 0, 1, 1 +Etna:21, 1, 0, 0, 1, 1 +Edgemont:21, 1, 0, 0, 1, 1 +Dexter:21, 1, 0, 0, 1, 1 +Crestwood:21, 1, 0, 0, 1, 1 +Crescent:21, 1, 0, 0, 1, 1 +Covington:21, 1, 0, 0, 1, 1 +Country Club Estates:21, 1, 0, 0, 1, 1 +Coleman:21, 1, 0, 0, 1, 1 +Chelsea:21, 1, 0, 0, 1, 1 +Chapman:21, 1, 0, 0, 1, 1 +Cedarville:21, 1, 0, 0, 1, 1 +Burton:21, 1, 0, 0, 1, 1 +Bryant:21, 1, 0, 0, 1, 1 +Browntown:21, 1, 0, 0, 1, 1 +Beverly:21, 1, 0, 0, 1, 1 +Beaver:21, 1, 0, 0, 1, 1 +Baxter:21, 1, 0, 0, 1, 1 +Barton:21, 1, 0, 0, 1, 1 +Ashton:21, 1, 0, 0, 1, 1 +Armstrong:21, 1, 0, 0, 1, 1 +Afton:21, 1, 0, 0, 1, 1 +Warsaw:20, 1, 0, 0, 1, 1 +Viola:20, 1, 0, 0, 1, 1 +Uniontown:20, 1, 0, 0, 1, 1 +Twin Oaks:20, 1, 0, 0, 1, 1 +Sweetwater:20, 1, 0, 0, 1, 1 +Stonewall:20, 1, 0, 0, 1, 1 +Spring Lake:20, 1, 0, 0, 1, 1 +Sparta:20, 1, 0, 0, 1, 1 +Ruby:20, 1, 0, 0, 1, 1 +Roseland:20, 1, 0, 0, 1, 1 +Pleasantville:20, 1, 0, 0, 1, 1 +Pittsburg:20, 1, 0, 0, 1, 1 +Pioneer:20, 1, 0, 0, 1, 1 +Pineville:20, 1, 0, 0, 1, 1 +Oak Forest:20, 1, 0, 0, 1, 1 +Northfield:20, 1, 0, 0, 1, 1 +Mount Airy:20, 1, 0, 0, 1, 1 +Maxwell:20, 1, 0, 0, 1, 1 +Marietta:20, 1, 0, 0, 1, 1 +Lamont:20, 1, 0, 0, 1, 1 +Lake View:20, 1, 0, 0, 1, 1 +Knoxville:20, 1, 0, 0, 1, 1 +Idlewild:20, 1, 0, 0, 1, 1 +Holt:20, 1, 0, 0, 1, 1 +Hastings:20, 1, 0, 0, 1, 1 +Hancock:20, 1, 0, 0, 1, 1 +Gilbert:20, 1, 0, 0, 1, 1 +Gardner:20, 1, 0, 0, 1, 1 +Freedom:20, 1, 0, 0, 1, 1 +Emerson:20, 1, 0, 0, 1, 1 +Echo:20, 1, 0, 0, 1, 1 +Dixon:20, 1, 0, 0, 1, 1 +Denver:20, 1, 0, 0, 1, 1 +Denton:20, 1, 0, 0, 1, 1 +Curtis:20, 1, 0, 0, 1, 1 +Creston:20, 1, 0, 0, 1, 1 +Clark:20, 1, 0, 0, 1, 1 +Chesterfield:20, 1, 0, 0, 1, 1 +Cedar Point:20, 1, 0, 0, 1, 1 +Cedar Creek:20, 1, 0, 0, 1, 1 +Cascade:20, 1, 0, 0, 1, 1 +Brandon:20, 1, 0, 0, 1, 1 +Blaine:20, 1, 0, 0, 1, 1 +Bancroft:20, 1, 0, 0, 1, 1 +Avalon:20, 1, 0, 0, 1, 1 +Atwood:20, 1, 0, 0, 1, 1 +Alma:20, 1, 0, 0, 1, 1 +Wheatland:19, 1, 0, 0, 1, 1 +Wells:19, 1, 0, 0, 1, 1 +Sun Valley:19, 1, 0, 0, 1, 1 +State Line:19, 1, 0, 0, 1, 1 +Silver City:19, 1, 0, 0, 1, 1 +Seneca:19, 1, 0, 0, 1, 1 +Selma:19, 1, 0, 0, 1, 1 +Saint Joseph:19, 1, 0, 0, 1, 1 +Saint John:19, 1, 0, 0, 1, 1 +Ross:19, 1, 0, 0, 1, 1 +Roberts:19, 1, 0, 0, 1, 1 +Reynolds:19, 1, 0, 0, 1, 1 +Red Rock:19, 1, 0, 0, 1, 1 +Ogden:19, 1, 0, 0, 1, 1 +Newark:19, 1, 0, 0, 1, 1 +New London:19, 1, 0, 0, 1, 1 +Mineral Springs:19, 1, 0, 0, 1, 1 +Meridian:19, 1, 0, 0, 1, 1 +Lynn:19, 1, 0, 0, 1, 1 +Lisbon:19, 1, 0, 0, 1, 1 +Lamar:19, 1, 0, 0, 1, 1 +Knollwood:19, 1, 0, 0, 1, 1 +Kensington:19, 1, 0, 0, 1, 1 +Horton:19, 1, 0, 0, 1, 1 +Homewood:19, 1, 0, 0, 1, 1 +Homestead:19, 1, 0, 0, 1, 1 +Holiday Hills:19, 1, 0, 0, 1, 1 +Henry:19, 1, 0, 0, 1, 1 +Harper:19, 1, 0, 0, 1, 1 +Greenbriar:19, 1, 0, 0, 1, 1 +Granville:19, 1, 0, 0, 1, 1 +Graham:19, 1, 0, 0, 1, 1 +Grafton:19, 1, 0, 0, 1, 1 +Genoa:19, 1, 0, 0, 1, 1 +Fruitland:19, 1, 0, 0, 1, 1 +Fremont:19, 1, 0, 0, 1, 1 +Forest Hill:19, 1, 0, 0, 1, 1 +Forest Grove:19, 1, 0, 0, 1, 1 +Folsom:19, 1, 0, 0, 1, 1 +Flint Hill:19, 1, 0, 0, 1, 1 +Fillmore:19, 1, 0, 0, 1, 1 +Ferndale:19, 1, 0, 0, 1, 1 +Fayette:19, 1, 0, 0, 1, 1 +Fairmont:19, 1, 0, 0, 1, 1 +Eastwood:19, 1, 0, 0, 1, 1 +Dudley:19, 1, 0, 0, 1, 1 +Dublin:19, 1, 0, 0, 1, 1 +Dogtown:19, 1, 0, 0, 1, 1 +Dawson:19, 1, 0, 0, 1, 1 +Cunningham:19, 1, 0, 0, 1, 1 +Conway:19, 1, 0, 0, 1, 1 +Collins:19, 1, 0, 0, 1, 1 +College Park:19, 1, 0, 0, 1, 1 +Chestnut Grove:19, 1, 0, 0, 1, 1 +Cherry Grove:19, 1, 0, 0, 1, 1 +Cherokee:19, 1, 0, 0, 1, 1 +Chatham:19, 1, 0, 0, 1, 1 +Camelot:19, 1, 0, 0, 1, 1 +Brooks:19, 1, 0, 0, 1, 1 +Big Springs:19, 1, 0, 0, 1, 1 +Big Creek:19, 1, 0, 0, 1, 1 +Benson:19, 1, 0, 0, 1, 1 +Bedford:19, 1, 0, 0, 1, 1 +Bartlett:19, 1, 0, 0, 1, 1 +Bailey:19, 1, 0, 0, 1, 1 +Atlanta:19, 1, 0, 0, 1, 1 +Alpha:19, 1, 0, 0, 1, 1 +Woodland Hills:18, 1, 0, 0, 1, 1 +Winfield:18, 1, 0, 0, 1, 1 +Willow Springs:18, 1, 0, 0, 1, 1 +Willow Grove:18, 1, 0, 0, 1, 1 +Williamstown:18, 1, 0, 0, 1, 1 +Wheeler:18, 1, 0, 0, 1, 1 +Westchester:18, 1, 0, 0, 1, 1 +Walton:18, 1, 0, 0, 1, 1 +Tyler:18, 1, 0, 0, 1, 1 +Taylorsville:18, 1, 0, 0, 1, 1 +Stillwater:18, 1, 0, 0, 1, 1 +Shawnee:18, 1, 0, 0, 1, 1 +Shamrock:18, 1, 0, 0, 1, 1 +Scott:18, 1, 0, 0, 1, 1 +Sanford:18, 1, 0, 0, 1, 1 +Saint Charles:18, 1, 0, 0, 1, 1 +Rocky Hill:18, 1, 0, 0, 1, 1 +Rockford:18, 1, 0, 0, 1, 1 +Ripley:18, 1, 0, 0, 1, 1 +Pulaski:18, 1, 0, 0, 1, 1 +Phillips:18, 1, 0, 0, 1, 1 +Pearl:18, 1, 0, 0, 1, 1 +New Market:18, 1, 0, 0, 1, 1 +New Boston:18, 1, 0, 0, 1, 1 +Nebo:18, 1, 0, 0, 1, 1 +Mount Tabor:18, 1, 0, 0, 1, 1 +Morton:18, 1, 0, 0, 1, 1 +Moore:18, 1, 0, 0, 1, 1 +Long Branch:18, 1, 0, 0, 1, 1 +London:18, 1, 0, 0, 1, 1 +Lewisville:18, 1, 0, 0, 1, 1 +Lewiston:18, 1, 0, 0, 1, 1 +Jerusalem:18, 1, 0, 0, 1, 1 +Jasper:18, 1, 0, 0, 1, 1 +Hadley:18, 1, 0, 0, 1, 1 +Grove:18, 1, 0, 0, 1, 1 +Greenbrier:18, 1, 0, 0, 1, 1 +Gray:18, 1, 0, 0, 1, 1 +Glasgow:18, 1, 0, 0, 1, 1 +Fletcher:18, 1, 0, 0, 1, 1 +Fair Oaks:18, 1, 0, 0, 1, 1 +Essex:18, 1, 0, 0, 1, 1 +Elwood:18, 1, 0, 0, 1, 1 +Eldorado:18, 1, 0, 0, 1, 1 +Dunlap:18, 1, 0, 0, 1, 1 +Dunbar:18, 1, 0, 0, 1, 1 +Decatur:18, 1, 0, 0, 1, 1 +Darlington:18, 1, 0, 0, 1, 1 +Damascus:18, 1, 0, 0, 1, 1 +Crystal Springs:18, 1, 0, 0, 1, 1 +Crestview:18, 1, 0, 0, 1, 1 +Coldwater:18, 1, 0, 0, 1, 1 +Climax:18, 1, 0, 0, 1, 1 +Center Hill:18, 1, 0, 0, 1, 1 +Carrollton:18, 1, 0, 0, 1, 1 +Carlton:18, 1, 0, 0, 1, 1 +Caldwell:18, 1, 0, 0, 1, 1 +Byron:18, 1, 0, 0, 1, 1 +Browns Corner:18, 1, 0, 0, 1, 1 +Bridgewater:18, 1, 0, 0, 1, 1 +Bolton:18, 1, 0, 0, 1, 1 +Blue Springs:18, 1, 0, 1, 0, 30 +Bloomingdale:18, 1, 0, 1, 0, 30 +Blair:18, 1, 0, 1, 0, 30 +Bethesda:18, 1, 0, 1, 0, 30 +Belleville:18, 1, 0, 1, 0, 30 +Bear Creek:18, 1, 0, 1, 0, 30 +Bayview:18, 1, 0, 1, 0, 30 +Avoca:18, 1, 0, 1, 0, 30 +Argyle:18, 1, 0, 1, 0, 30 +Wyoming:17, 1, 0, 1, 0, 30 +Woodland Park:17, 1, 0, 1, 0, 30 +Wilton:17, 1, 0, 1, 0, 30 +White Hall:17, 1, 0, 1, 0, 30 +Watkins:17, 1, 0, 1, 0, 30 +Warwick:17, 1, 0, 1, 0, 30 +Walnut:17, 1, 0, 1, 0, 30 +Vienna:17, 1, 0, 1, 0, 30 +Union City:17, 1, 0, 1, 0, 30 +Texas:17, 1, 0, 1, 0, 30 +Sumner:17, 1, 0, 1, 0, 30 +Summerfield:17, 1, 0, 1, 0, 30 +Sugar Hill:17, 1, 0, 1, 0, 30 +Stratford:17, 1, 0, 1, 0, 30 +Springtown:17, 1, 0, 1, 0, 30 +Saratoga:17, 1, 0, 1, 0, 30 +San Jose:17, 1, 0, 1, 0, 30 +Saint Johns:17, 1, 0, 1, 0, 30 +Royal:17, 1, 0, 1, 0, 30 +Rosebud:17, 1, 0, 1, 0, 30 +Rockwood:17, 1, 0, 1, 0, 30 +Riley:17, 1, 0, 1, 0, 30 +Red Oak:17, 1, 0, 1, 0, 30 +Quincy:17, 1, 0, 1, 0, 30 +Pomona:17, 1, 0, 1, 0, 30 +Point Pleasant:17, 1, 0, 1, 0, 30 +Philadelphia:17, 1, 0, 1, 0, 30 +Peoria:17, 1, 0, 1, 0, 30 +Murray:17, 1, 0, 1, 0, 30 +Millbrook:17, 1, 0, 1, 0, 30 +Maywood:17, 1, 0, 1, 0, 30 +Macon:17, 1, 0, 1, 0, 30 +Lone Oak:17, 1, 0, 1, 0, 30 +Lewis:17, 1, 0, 1, 0, 30 +Leon:17, 1, 0, 1, 0, 30 +Lee:17, 1, 0, 1, 0, 30 +Jones:17, 1, 0, 1, 0, 30 +Hubbard:17, 1, 0, 1, 0, 30 +Gum Springs:17, 1, 0, 1, 0, 30 +Guilford:17, 1, 0, 1, 0, 30 +Galena:17, 1, 0, 1, 0, 30 +Frenchtown:17, 1, 0, 1, 0, 30 +Freeport:17, 1, 0, 1, 0, 30 +Frankfort:17, 1, 0, 1, 0, 30 +Fowler:17, 1, 0, 1, 0, 30 +Floyd:17, 1, 0, 1, 0, 30 +Fisher:17, 1, 0, 1, 0, 30 +Fairbanks:17, 1, 0, 1, 0, 30 +Evans:17, 1, 0, 1, 0, 30 +Empire:17, 1, 0, 1, 0, 30 +Elm Grove:17, 1, 0, 1, 0, 30 +Ellsworth:17, 1, 0, 1, 0, 30 +Edgewater:17, 1, 0, 1, 0, 30 +Dewey:17, 1, 0, 1, 0, 30 +Derby:17, 1, 0, 1, 0, 30 +Denmark:17, 1, 0, 1, 0, 30 +Cordova:17, 1, 0, 1, 0, 30 +Colonial Heights:17, 1, 0, 1, 0, 30 +Colfax:17, 1, 0, 1, 0, 30 +Clearview:17, 1, 0, 1, 0, 30 +Chestnut Ridge:17, 1, 0, 1, 0, 30 +Carthage:17, 1, 0, 1, 0, 30 +Carpenter:17, 1, 0, 1, 0, 30 +Calhoun:17, 1, 0, 1, 0, 30 +Brookwood:17, 1, 0, 1, 0, 30 +Brookville:17, 1, 0, 1, 0, 30 +Brentwood:17, 1, 0, 1, 0, 30 +Birmingham:17, 1, 0, 1, 0, 30 +Arcola:17, 1, 0, 1, 0, 30 +Andover:17, 1, 0, 1, 0, 30 +Aberdeen:17, 1, 0, 1, 0, 30 +Yorktown:16, 1, 0, 1, 0, 30 +Wright:16, 1, 0, 1, 0, 30 +Woodrow:16, 1, 0, 1, 0, 30 +Woodbury:16, 1, 0, 1, 0, 30 +Winslow:16, 1, 0, 1, 0, 30 +Whitney:16, 1, 0, 1, 0, 30 +Whispering Pines:16, 1, 0, 1, 0, 30 +Welcome:16, 1, 0, 1, 0, 30 +Webb:16, 1, 0, 1, 0, 30 +Washington Heights:16, 1, 0, 1, 0, 30 +Summerville:16, 1, 0, 1, 0, 30 +Sullivan:16, 1, 0, 1, 0, 30 +Stewart:16, 1, 0, 1, 0, 30 +Spring Grove:16, 1, 0, 1, 0, 30 +Silver Springs:16, 1, 0, 1, 0, 30 +Sheffield:16, 1, 0, 1, 0, 30 +Shannon:16, 1, 0, 1, 0, 30 +Scottsville:16, 1, 0, 1, 0, 30 +Saint George:16, 1, 0, 1, 0, 30 +Ryan:16, 1, 0, 1, 0, 30 +Ruth:16, 1, 0, 1, 0, 30 +Roy:16, 1, 0, 1, 0, 30 +Roxbury:16, 1, 0, 1, 0, 30 +Rosewood:16, 1, 0, 1, 0, 30 +Roscoe:16, 1, 0, 1, 0, 30 +Rocky Point:16, 1, 0, 1, 0, 30 +Richfield:16, 1, 0, 1, 0, 30 +Richardson:16, 1, 0, 1, 0, 30 +Proctor:16, 1, 0, 1, 0, 30 +Pinecrest:16, 1, 0, 1, 0, 30 +Pine Valley:16, 1, 0, 1, 0, 30 +Pierce:16, 1, 0, 1, 0, 30 +Perkins:16, 1, 0, 1, 0, 30 +Paxton:16, 1, 0, 1, 0, 30 +Omega:16, 1, 0, 1, 0, 30 +Nottingham:16, 1, 0, 1, 0, 30 +Montezuma:16, 1, 0, 1, 0, 30 +Montague:16, 1, 0, 1, 0, 30 +Milo:16, 1, 0, 1, 0, 30 +Milan:16, 1, 0, 1, 0, 30 +Martinsville:16, 1, 0, 1, 0, 30 +Maple Hill:16, 1, 0, 1, 0, 30 +Ludlow:16, 1, 0, 1, 0, 30 +Louisville:16, 1, 0, 1, 0, 30 +Longwood:16, 1, 0, 1, 0, 30 +Lewisburg:16, 1, 0, 1, 0, 30 +Lenox:16, 1, 0, 1, 0, 30 +Leesville:16, 1, 0, 1, 0, 30 +Leesburg:16, 1, 0, 1, 0, 30 +Lawrenceville:16, 1, 0, 1, 0, 30 +Kirkland:16, 1, 0, 1, 0, 30 +Kelly:16, 1, 0, 1, 0, 30 +Jerome:16, 1, 0, 1, 0, 30 +Jenkins:16, 1, 0, 1, 0, 30 +Indian Village:16, 1, 0, 1, 0, 30 +Hurricane:16, 1, 0, 1, 0, 30 +Howell:16, 1, 0, 1, 0, 30 +Hillcrest:16, 1, 0, 1, 0, 30 +Hidden Valley:16, 1, 0, 1, 0, 30 +Harvey:16, 1, 0, 1, 0, 30 +Harmon:16, 1, 0, 1, 0, 30 +Greendale:16, 1, 0, 1, 0, 30 +Granite:16, 1, 0, 1, 0, 30 +Glenville:16, 1, 0, 1, 0, 30 +Gladstone:16, 1, 0, 1, 0, 30 +Gilmore:16, 1, 0, 1, 0, 30 +Garrison:16, 1, 0, 1, 0, 30 +Freeman:16, 1, 0, 1, 0, 30 +Fox:16, 1, 0, 1, 0, 30 +Forestville:16, 1, 0, 1, 0, 30 +Flatwoods:16, 1, 0, 1, 0, 30 +Elkton:16, 1, 0, 1, 0, 30 +Elizabeth:16, 1, 0, 1, 0, 30 +Elba:16, 1, 0, 1, 0, 30 +Cumberland:16, 1, 0, 1, 0, 30 +Clearwater:16, 1, 0, 1, 0, 30 +Carter:16, 1, 0, 1, 0, 30 +California:16, 1, 0, 1, 0, 30 +Caledonia:16, 1, 0, 1, 0, 30 +Burns:16, 1, 0, 1, 0, 30 +Buckingham:16, 1, 0, 1, 0, 30 +Brunswick:16, 1, 0, 1, 0, 30 +Bennett:16, 1, 0, 1, 0, 30 +Bay View:16, 1, 0, 1, 0, 30 +Barnes:16, 1, 0, 1, 0, 30 +Arthur:16, 1, 0, 1, 0, 30 +Appleton:16, 1, 0, 1, 0, 30 +Amherst:16, 1, 0, 1, 0, 30 +Allison:16, 1, 0, 1, 0, 30 +Allentown:16, 1, 0, 1, 0, 30 +Acme:16, 1, 0, 1, 0, 30 +Woodbine:15, 1, 0, 1, 0, 30 +Wolf Creek:15, 1, 0, 1, 0, 30 +Williamsville:15, 1, 0, 1, 0, 30 +White Plains:15, 1, 0, 1, 0, 30 +Wesley:15, 1, 0, 1, 0, 30 +Weldon:15, 1, 0, 1, 0, 30 +Wayland:15, 1, 0, 1, 0, 30 +Tyrone:15, 1, 0, 1, 0, 30 +Tremont:15, 1, 0, 1, 0, 30 +Tracy:15, 1, 0, 1, 0, 30 +Tipton:15, 1, 0, 1, 0, 30 +Thompsonville:15, 1, 0, 1, 0, 30 +Tanglewood:15, 1, 0, 1, 0, 30 +Tabor:15, 1, 0, 1, 0, 30 +Sutton:15, 1, 0, 1, 0, 30 +Superior:15, 1, 0, 1, 0, 30 +Star:15, 1, 0, 1, 0, 30 +Stafford:15, 1, 0, 1, 0, 30 +Sleepy Hollow:15, 1, 0, 1, 0, 30 +Siloam:15, 1, 0, 1, 0, 30 +Shelby:15, 1, 0, 1, 0, 30 +Shaw:15, 1, 0, 1, 0, 30 +Sawyer:15, 1, 0, 1, 0, 30 +Saint James:15, 1, 0, 1, 0, 30 +Saint Clair:15, 1, 0, 1, 0, 30 +Red Bank:15, 1, 0, 1, 0, 30 +Rankin:15, 1, 0, 1, 0, 30 +Price:15, 1, 0, 1, 0, 30 +Peru:15, 1, 0, 1, 0, 30 +Page:15, 1, 0, 1, 0, 30 +Owens:15, 1, 0, 1, 0, 30 +Oneida:15, 1, 0, 1, 0, 30 +Northwood:15, 1, 0, 1, 0, 30 +Nichols:15, 1, 0, 1, 0, 30 +New Town:15, 1, 0, 1, 0, 30 +Murphy:15, 1, 0, 1, 0, 30 +Morris:15, 1, 0, 1, 0, 30 +Morgantown:15, 1, 0, 1, 0, 30 +Montpelier:15, 1, 0, 1, 0, 30 +Mechanicsburg:15, 1, 0, 1, 0, 30 +Lucas:15, 1, 0, 1, 0, 30 +Lone Pine:15, 1, 0, 1, 0, 30 +Littleton:15, 1, 0, 1, 0, 30 +Little River:15, 1, 0, 1, 0, 30 +Leland:15, 1, 0, 1, 0, 30 +Langdon:15, 1, 0, 1, 0, 30 +Lakeville:15, 1, 0, 1, 0, 30 +Lake Forest:15, 1, 0, 1, 0, 30 +La Grange:15, 1, 0, 1, 0, 30 +King:15, 1, 0, 1, 0, 30 +Kimball:15, 1, 0, 1, 0, 30 +Johnsonville:15, 1, 0, 1, 0, 30 +Highland Park:15, 1, 0, 1, 0, 30 +Hazelwood:15, 1, 0, 1, 0, 30 +Hartland:15, 1, 0, 1, 0, 30 +Hardy:15, 1, 0, 1, 0, 30 +Guthrie:15, 1, 0, 1, 0, 30 +Griffin:15, 1, 0, 1, 0, 30 +Gravel Hill:15, 1, 0, 1, 0, 30 +Golden:15, 1, 0, 1, 0, 30 +Globe:15, 1, 0, 1, 0, 30 +Gary:15, 1, 0, 1, 0, 30 +Frogtown:15, 1, 0, 1, 0, 30 +Four Points:15, 1, 0, 1, 0, 30 +Forest:15, 1, 0, 1, 0, 30 +Flint:15, 1, 0, 1, 0, 30 +Ferguson:15, 1, 0, 1, 0, 30 +Fayetteville:15, 1, 0, 1, 0, 30 +Farmersville:15, 1, 0, 1, 0, 30 +Ellisville:15, 1, 0, 1, 0, 30 +Edwards:15, 1, 0, 1, 0, 30 +Edgewood:15, 1, 0, 1, 0, 30 +Doyle:15, 1, 0, 1, 0, 30 +Delmar:15, 1, 0, 1, 0, 30 +Dallas:15, 1, 0, 1, 0, 30 +Crystal:15, 1, 0, 1, 0, 30 +Collinsville:15, 1, 0, 1, 0, 30 +Clifford:15, 1, 0, 1, 0, 30 +Church Hill:15, 1, 0, 1, 0, 30 +Cherry Valley:15, 1, 0, 1, 0, 30 +Cedar:15, 1, 0, 1, 0, 30 +Brookwood:15, 1, 0, 1, 0, 30 +Blanchard:15, 1, 0, 1, 0, 30 +Berea:15, 1, 0, 1, 0, 30 +Belleview:15, 1, 0, 1, 0, 30 +Belfast:15, 1, 0, 1, 0, 30 +Bayside:15, 1, 0, 1, 0, 30 +Bath:15, 1, 0, 1, 0, 30 +Avery:15, 1, 0, 1, 0, 30 +Ashley:15, 1, 0, 1, 0, 30 +Amity:15, 1, 0, 1, 0, 30 +Altamont:15, 1, 0, 1, 0, 30 +Adrian:15, 1, 0, 1, 0, 30 +Youngstown:14, 1, 0, 1, 0, 30 +Woodruff:14, 1, 0, 1, 0, 30 +Woodcrest:14, 1, 0, 1, 0, 30 +Willow:14, 1, 0, 1, 0, 30 +Willis:14, 1, 0, 1, 0, 30 +Wildwood:14, 1, 0, 1, 0, 30 +Whitesville:14, 1, 0, 1, 0, 30 +Westminster:14, 1, 0, 1, 0, 30 +Westgate:14, 1, 0, 1, 0, 30 +West Liberty:14, 1, 0, 1, 0, 30 +Vista:14, 1, 0, 1, 0, 30 +Vance:14, 1, 0, 1, 0, 30 +The Meadows:14, 1, 0, 1, 0, 30 +Taft:14, 1, 0, 1, 0, 30 +Springhill:14, 1, 0, 1, 0, 30 +Somerville:14, 1, 0, 1, 0, 30 +Snug Harbor:14, 1, 0, 1, 0, 30 +Smith:14, 1, 0, 1, 0, 30 +Silver Creek:14, 1, 0, 1, 0, 30 +Shore Acres:14, 1, 0, 1, 0, 30 +Rutland:14, 1, 0, 1, 0, 30 +Rossville:14, 1, 0, 1, 0, 30 +Rolling Hills:14, 1, 0, 1, 0, 30 +River Oaks:14, 1, 0, 1, 0, 30 +Ridgeville:14, 1, 0, 1, 0, 30 +Richville:14, 1, 0, 1, 0, 30 +Riceville:14, 1, 0, 1, 0, 30 +Reno:14, 1, 0, 1, 0, 30 +Redland:14, 1, 0, 1, 0, 30 +Prosperity:14, 1, 0, 1, 0, 30 +Post Oak:14, 1, 0, 1, 0, 30 +Plainville:14, 1, 0, 1, 0, 30 +Pinhook:14, 1, 0, 1, 0, 30 +Phoenix:14, 1, 0, 1, 0, 30 +Payne:14, 1, 0, 1, 0, 30 +Parkwood:14, 1, 0, 1, 0, 30 + diff --git a/tpcdsgen/data/colors.dst b/tpcdsgen/data/colors.dst new file mode 100644 index 00000000..88b5cf3a --- /dev/null +++ b/tpcdsgen/data/colors.dst @@ -0,0 +1,102 @@ +------ +-- colors +-- values weights +-- ----------------------- +-- 1. color 1. uniform +-- 2. skewed dist (used by dbgen) +-- 3. low likelihood (used by qgen) +-- 4. medium likelihood (used by qgen) +-- 5. high likelihood (used by qgen) +------ +almond: 1, 1, 1, 0, 0 +antique: 1, 1, 1, 0, 0 +aquamarine: 1, 1, 1, 0, 0 +azure: 1, 1, 1, 0, 0 +beige: 1, 1, 1, 0, 0 +bisque: 1, 1, 1, 0, 0 +black: 1, 1, 1, 0, 0 +blanched: 1, 1, 1, 0, 0 +blue: 1, 1, 1, 0, 0 +blush: 1, 1, 1, 0, 0 +brown: 1, 1, 1, 0, 0 +burlywood: 1, 1, 1, 0, 0 +burnished: 1, 1, 1, 0, 0 +chartreuse: 1, 1, 1, 0, 0 +chiffon: 1, 1, 1, 0, 0 +chocolate: 1, 1, 1, 0, 0 +coral: 1, 1, 1, 0, 0 +cornflower: 1, 1, 1, 0, 0 +cornsilk: 1, 1, 1, 0, 0 +cream: 1, 1, 1, 0, 0 +cyan: 1, 1, 1, 0, 0 +dark: 1, 1, 1, 0, 0 +deep: 1, 1, 1, 0, 0 +dim: 1, 1, 1, 0, 0 +dodger: 1, 1, 1, 0, 0 +drab: 1, 1, 1, 0, 0 +firebrick: 1, 1, 1, 0, 0 +floral: 1, 1, 1, 0, 0 +forest: 1, 1, 1, 0, 0 +frosted: 1, 1, 1, 0, 0 +gainsboro: 1, 3, 0, 1, 0 +ghost: 1, 3, 0, 1, 0 +goldenrod: 1, 3, 0, 1, 0 +green: 1, 3, 0, 1, 0 +grey: 1, 3, 0, 1, 0 +honeydew: 1, 3, 0, 1, 0 +hot: 1, 3, 0, 1, 0 +indian: 1, 3, 0, 1, 0 +ivory: 1, 3, 0, 1, 0 +khaki: 1, 3, 0, 1, 0 +lace: 1, 3, 0, 1, 0 +lavender: 1, 3, 0, 1, 0 +lawn: 1, 3, 0, 1, 0 +lemon: 1, 3, 0, 1, 0 +light: 1, 3, 0, 1, 0 +lime: 1, 3, 0, 1, 0 +linen: 1, 3, 0, 1, 0 +magenta: 1, 3, 0, 1, 0 +maroon: 1, 3, 0, 1, 0 +medium: 1, 3, 0, 1, 0 +metallic: 1, 3, 0, 1, 0 +midnight: 1, 3, 0, 1, 0 +mint: 1, 3, 0, 1, 0 +misty: 1, 3, 0, 1, 0 +moccasin: 1, 3, 0, 1, 0 +navajo: 1, 3, 0, 1, 0 +navy: 1, 3, 0, 1, 0 +olive: 1, 3, 0, 1, 0 +orange: 1, 3, 0, 1, 0 +orchid: 1, 3, 0, 1, 0 +pale: 1, 9, 0, 0, 1 +papaya: 1, 9, 0, 0, 1 +peach: 1, 9, 0, 0, 1 +peru: 1, 9, 0, 0, 1 +pink: 1, 9, 0, 0, 1 +plum: 1, 9, 0, 0, 1 +powder: 1, 9, 0, 0, 1 +puff: 1, 9, 0, 0, 1 +purple: 1, 9, 0, 0, 1 +red: 1, 9, 0, 0, 1 +rose: 1, 9, 0, 0, 1 +rosy: 1, 9, 0, 0, 1 +royal: 1, 9, 0, 0, 1 +saddle: 1, 9, 0, 0, 1 +salmon: 1, 9, 0, 0, 1 +sandy: 1, 9, 0, 0, 1 +seashell: 1, 9, 0, 0, 1 +sienna: 1, 9, 0, 0, 1 +sky: 1, 9, 0, 0, 1 +slate: 1, 9, 0, 0, 1 +smoke: 1, 9, 0, 0, 1 +snow: 1, 9, 0, 0, 1 +spring: 1, 9, 0, 0, 1 +steel: 1, 9, 0, 0, 1 +tan: 1, 9, 0, 0, 1 +thistle: 1, 9, 0, 0, 1 +tomato: 1, 9, 0, 0, 1 +turquoise: 1, 9, 0, 0, 1 +violet: 1, 9, 0, 0, 1 +wheat: 1, 9, 0, 0, 1 +white: 1, 9, 0, 0, 1 +yellow: 1, 9, 0, 0, 1 diff --git a/tpcdsgen/data/countries.dst b/tpcdsgen/data/countries.dst new file mode 100644 index 00000000..0a93754e --- /dev/null +++ b/tpcdsgen/data/countries.dst @@ -0,0 +1,246 @@ +------ +-- coutries +-- values weights +-- ====== ======= +-- 1. country 1. uniform +------ +AFGHANISTAN:1 +ALAND ISLANDS:1 +ALBANIA:1 +ALGERIA:1 +AMERICAN SAMOA:1 +ANDORRA:1 +ANGOLA:1 +ANGUILLA:1 +ANTARCTICA:1 +ANTIGUA AND BARBUDA:1 +ARGENTINA:1 +ARMENIA:1 +ARUBA:1 +AUSTRALIA:1 +AUSTRIA:1 +AZERBAIJAN:1 +BAHAMAS:1 +BAHRAIN:1 +BANGLADESH:1 +BARBADOS:1 +BELARUS:1 +BELGIUM:1 +BELIZE:1 +BENIN:1 +BERMUDA:1 +BHUTAN:1 +BOLIVIA:1 +-- BOSNIA AND HERZEGOVINA:1 +BOTSWANA:1 +BOUVET ISLAND:1 +BRAZIL:1 +-- BRITISH INDIAN OCEAN TERRITORY:1 +BRUNEI DARUSSALAM:1 +BULGARIA:1 +BURKINA FASO:1 +BURUNDI:1 +CAMBODIA:1 +CAMEROON:1 +CANADA:1 +CAPE VERDE:1 +CAYMAN ISLANDS:1 +-- add(CENTRAL AFRICAN REPUBLIC:1 +CHILE:1 +CHINA:1 +CHRISTMAS ISLAND:1 +--COCOS (KEELING) ISLANDS:1 +COMOROS:1 +--CONGO, THE DEMOCRATIC REPUBLIC OF THE:1 +-- add (COOK ISLANDS:1 +COSTA RICA:1 +CTE D'IVOIRE:1 +CROATIA:1 +CUBA:1 +CYPRUS:1 +CZECH REPUBLIC:1 +DENMARK:1 +DJIBOUTI:1 +DOMINICA:1 +ECUADOR:1 +EGYPT:1 +EL SALVADOR:1 +EQUATORIAL GUINEA:1 +ERITREA:1 +ESTONIA:1 +ETHIOPIA:1 +--FALKLAND ISLANDS (MALVINAS):1 +FAROE ISLANDS:1 +FIJI:1 +FINLAND:1 +FRANCE:1 +FRENCH GUIANA:1 +FRENCH POLYNESIA:1 +--FRENCH SOUTHERN TERRITORIES:1 +GABON:1 +GAMBIA:1 +GEORGIA:1 +GERMANY:1 +GHANA:1 +GIBRALTAR:1 +GREECE:1 +GREENLAND:1 +GRENADA:1 +GUADELOUPE:1 +GUAM:1 +GUATEMALA:1 +GUERNSEY:1 +GUINEA:1 +GUINEA-BISSAU:1 +GUYANA:1 +HAITI:1 +--HEARD ISLAND AND MCDONALD ISLANDS:1 +--HOLY SEE (VATICAN CITY STATE):1 +HONDURAS:1 +HONG KONG:1 +HUNGARY:1 +ICELAND:1 +INDIA:1 +INDONESIA:1 +--IRAN, ISLAMIC REPUBLIC OF:1 +IRAQ:1 +IRELAND:1 +ISLE OF MAN:1 +ISRAEL:1 +ITALY:1 +JAMAICA:1 +JAPAN:1 +JERSEY:1 +JORDAN:1 +KAZAKHSTAN:1 +KENYA:1 +KIRIBATI:1 +--KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF:1 +KOREA\, REPUBLIC OF:1 +KUWAIT:1 +KYRGYZSTAN:1 +--LAO PEOPLE'S DEMOCRATIC REPUBLIC:1 +LATVIA:1 +LEBANON:1 +LESOTHO:1 +LIBERIA:1 +--LIBYAN ARAB JAMAHIRIYA:1 +LIECHTENSTEIN:1 +LITHUANIA:1 +LUXEMBOURG:1 +MACAO:1 +--MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF:1 +MADAGASCAR:1 +MALAWI:1 +MALAYSIA:1 +MALDIVES:1 +MALI:1 +MALTA:1 +MARSHALL ISLANDS:1 +MARTINIQUE:1 +MAURITANIA:1 +MAURITIUS:1 +MAYOTTE:1 +MEXICO:1 +--MICRONESIA, FEDERATED STATES OF:1 +MOLDOVA\, REPUBLIC OF:1 +MONACO:1 +MONGOLIA:1 +MONTENEGRO:1 +MONTSERRAT:1 +MOROCCO:1 +MOZAMBIQUE:1 +MYANMAR:1 +NAMIBIA:1 +NAURU:1 +NEPAL:1 +NETHERLANDS:1 +NETHERLANDS ANTILLES:1 +NEW CALEDONIA:1 +NEW ZEALAND:1 +NICARAGUA:1 +NIGER:1 +NIGERIA:1 +NIUE:1 +NORFOLK ISLAND:1 +--NORTHERN MARIANA ISLANDS:1 +NORWAY:1 +OMAN:1 +PAKISTAN:1 +PALAU:1 +--PALESTINIAN TERRITORY, OCCUPIED:1 +PANAMA:1 +PAPUA NEW GUINEA:1 +PARAGUAY:1 +PERU:1 +PHILIPPINES:1 +PITCAIRN:1 +POLAND:1 +PORTUGAL:1 +PUERTO RICO:1 +QATAR:1 +RUNION:1 +ROMANIA:1 +RUSSIAN FEDERATION:1 +RWANDA:1 +SAINT HELENA:1 +--SAINT KITTS AND NEVIS:1 +SAINT LUCIA:1 +--SAINT PIERRE AND MIQUELON:1 +--SAINT VINCENT AND THE GRENADINES:1 +SAMOA:1 +SAN MARINO:1 +--SAO TOME AND PRINCIPE:1 +SAUDI ARABIA:1 +SENEGAL:1 +SERBIA:1 +SEYCHELLES:1 +SIERRA LEONE:1 +SINGAPORE:1 +SLOVAKIA:1 +SLOVENIA:1 +SOLOMON ISLANDS:1 +SOMALIA:1 +SOUTH AFRICA:1 +--SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS:1 +SPAIN:1 +SRI LANKA:1 +SUDAN:1 +SURINAME:1 +--SVALBARD AND JAN MAYEN:1 +SWAZILAND:1 +SWEDEN:1 +SWITZERLAND:1 +SYRIAN ARAB REPUBLIC:1 +--TAIWAN, PROVINCE OF CHINA:1 +TAJIKISTAN:1 +--TANZANIA, UNITED REPUBLIC OF:1 +THAILAND:1 +TIMOR-LESTE:1 +TOGO:1 +TOKELAU:1 +TONGA:1 +TRINIDAD AND TOBAGO:1 +TUNISIA:1 +TURKEY:1 +TURKMENISTAN:1 +--TURKS AND CAICOS ISLANDS:1 +TUVALU:1 +UGANDA:1 +UKRAINE:1 +UNITED ARAB EMIRATES:1 +UNITED KINGDOM:1 +UNITED STATES:1 +--UNITED STATES MINOR OUTLYING ISLANDS:1 +URUGUAY:1 +UZBEKISTAN:1 +VANUATU:1 +VENEZUELA:1 +VIET NAM:1 +--VIRGIN ISLANDS, BRITISH:1 +VIRGIN ISLANDS\, U.S.:1 +WALLIS AND FUTUNA:1 +WESTERN SAHARA:1 +YEMEN:1 +ZAMBIA:1 +ZIMBABWE:1 diff --git a/tpcdsgen/data/credit_ratings.dst b/tpcdsgen/data/credit_ratings.dst new file mode 100644 index 00000000..1b35b89f --- /dev/null +++ b/tpcdsgen/data/credit_ratings.dst @@ -0,0 +1,10 @@ +------ +-- credit_rating +-- values weights +-- ====== ======== +-- 1. credit rating 1. distribution +------ +Good: 65 +Low Risk: 25 +High Risk: 10 +Unknown: 4 diff --git a/tpcdsgen/data/dep_count.dst b/tpcdsgen/data/dep_count.dst new file mode 100644 index 00000000..7602e99c --- /dev/null +++ b/tpcdsgen/data/dep_count.dst @@ -0,0 +1,17 @@ +------ +-- dependent_count +------ +-- values weights +-- ====== ======= +-- 1. count 1. weight +----------------------- +0: 25 +1: 25 +2: 10 +3: 10 +4: 8 +5: 8 +6: 5 +7: 5 +8: 2 +9: 2 diff --git a/tpcdsgen/data/education.dst b/tpcdsgen/data/education.dst new file mode 100644 index 00000000..cd5a3239 --- /dev/null +++ b/tpcdsgen/data/education.dst @@ -0,0 +1,16 @@ +------ +-- education +-- values weights +-- ======= ======== +-- 1. level 1. uniform +-- 2. good credit distribution +-- 3. low credit distribution +-- 4. high risk credit distribution +------ +Primary: 1, 650, 250, 100 +Secondary: 1, 3250, 1250, 500 +College: 1, 650, 250, 100 +2 yr Degree: 1, 975, 375, 150 +4 yr Degree: 1, 650, 250, 100 +Advanced Degree: 1, 325, 125, 50 +Unknown: 1, 10, 10, 10 diff --git a/tpcdsgen/data/electronic_class.dst b/tpcdsgen/data/electronic_class.dst new file mode 100644 index 00000000..9e6d3708 --- /dev/null +++ b/tpcdsgen/data/electronic_class.dst @@ -0,0 +1,24 @@ +------ +-- electronic_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +cameras, 17: 1 +camcorders, 17: 1 +dvd/vcr players, 17: 1 +audio, 17: 1 +karoke, 17: 1 +musical, 17: 1 +personal, 17: 1 +scanners, 17: 1 +televisions, 17: 1 +memory, 17: 1 +disk drives, 17: 1 +monitors, 17: 1 +stereo, 17: 1 +automotive, 17: 1 +portable, 17: 1 +wireless, 17: 1 diff --git a/tpcdsgen/data/fips.dst b/tpcdsgen/data/fips.dst new file mode 100755 index 00000000..4be4e602 --- /dev/null +++ b/tpcdsgen/data/fips.dst @@ -0,0 +1,3185 @@ +-- +-- Legal Notice +-- +-- This document and associated source code (the Work) is a part of a +-- benchmark specification maintained by the TPC. +-- +-- The TPC reserves all right, title, and interest to the Work as provided +-- under U.S. and international laws, including without limitation all patent +-- and trademark rights therein. +-- +-- No Warranty +-- +-- 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION +-- CONTAINED HEREIN IS PROVIDED AS IS AND WITH ALL FAULTS, AND THE +-- AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER +-- WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, +-- INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES, +-- DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR +-- PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF +-- WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE. +-- ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, +-- QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT +-- WITH REGARD TO THE WORK. +-- 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO +-- ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE +-- COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS +-- OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT, +-- INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY, +-- OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT +-- RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD +-- ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. +-- +-- Contributors: +-- Gradient Systems +-- +-- +-- +-- + +-- values weights (aliases in parens) +-- ====== ======= +-- 1: FIPS code (fips) :int 1: uniform (uniform) +-- 2: county name (name) :varchar 2: population (population) +-- 3: state abreviation (st) :varchar 3: timezone weighting (tz) +-- 4: full state name (state) :varchar 4: in zone1 (tz90) +-- 5: ZIP prefix (zone) :int 5: in zone2 (tz9) +-- 6: gmt offset (gmt) :int 6: in zone3 (tz1) +-- +-- NOTE: missing data for St. Clair County, AL +-- NOTE: missing data for St. Francis County, AR +-- NOTE: missing data for St. Johns County, FL +-- NOTE: missing data for St. Lucie County, FL +-- NOTE: missing data for St. Clair County, IL +-- NOTE: missing data for St. Joseph County, IN +-- NOTE: missing data for St. Bernard Parish, LA +-- NOTE: missing data for St. Charles Parish, LA +-- NOTE: missing data for St. Helena Parish, LA +-- NOTE: missing data for St. John the Baptist Parish, LA +-- NOTE: missing data for St. Landry Parish, LA +-- NOTE: missing data for St. Martin Parish, LA +-- NOTE: missing data for St. Mary Parish, LA +-- NOTE: missing data for St. Tammany Parish, LA +-- NOTE: missing data for St. Mary's County, MD +-- NOTE: missing data for St. Clair County, MI +-- NOTE: missing data for St. Joseph County, MI +-- NOTE: missing data for St. Louis County, MN +-- NOTE: missing data for St. Charles County, MO +-- NOTE: missing data for St. Clair County, MO +-- NOTE: missing data for Ste. Genevieve County, MO +-- NOTE: missing data for St. Francois County, MO +-- NOTE: missing data for St. Louis County, MO +-- NOTE: missing data for St. Lawrence County, NY +-- NOTE: missing data for St. Croix County, WI +-- NOTE: outer ketchikan AK and some of the census areas from AK + +-- NOTE: apparently someone doesn't know how to spell Missouri. +------ +47187,Williamson County, TN, Tennesee, 3, -5:1, 117569, 1387, 1, 0, 0 +46137,Ziebach County, SD, South Dakota, 5, -6:1, 2176, 1148, 1, 0, 0 +01127,Walker County, AL, Alabama, 3, -6:1, 71027, 1148, 1, 0, 0 +45039,Fairfield County, SC, South Carolina, 2, -5:1, 22394, 1387, 1, 0, 0 +39139,Richland County, OH, Ohio, 4, -5:1, 127342, 1387, 1, 0, 0 +22041,Franklin Parish, LA, Louisiana, 7, -6:1, 22163, 1148, 1, 0, 0 +29061,Daviess County, MO, Mosourri, 6, -6:1, 7842, 1148, 1, 0, 0 +13013,Barrow County, GA, Georgia, 3, -5:1, 40344, 1387, 1, 0, 0 +26095,Luce County, MI, Michigan, 4, -5:1, 6640, 1387, 1, 0, 0 +22053,Jefferson Davis Parish, LA, Louisiana, 7, -6:1, 31607, 1148, 1, 0, 0 +35047,San Miguel County, NM, New Mexico, 8, -7:1, 28996, 4647, 1, 0, 0 +26063,Huron County, MI, Michigan, 4, -5:1, 35303, 1387, 1, 0, 0 +42043,Dauphin County, PA, Pennsylvania, 1, -5:1, 245579, 1387, 1, 0, 0 +36005,Bronx County, NY, New York, 1, -5:1, 1195599, 1387, 1, 0, 0 +53037,Kittitas County, WA, Washiington, 9, -8:1, 31714, 3240, 0, 1, 0 +01097,Mobile County, AL, Alabama, 3, -6:1, 399429, 1148, 1, 0, 0 +13221,Oglethorpe County, GA, Georgia, 3, -5:1, 11418, 1387, 1, 0, 0 +27159,Wadena County, MN, Minnesota, 5, -6:1, 13145, 1148, 1, 0, 0 +18099,Marshall County, IN, Indiana, 4, -5:1, 45444, 1387, 1, 0, 0 +27113,Pennington County, MN, Minnesota, 5, -6:1, 13562, 1148, 1, 0, 0 +12075,Levy County, FL, Florida, 3, -5:1, 31796, 1387, 1, 0, 0 +54081,Raleigh County, WV, West Virginia, 2, -5:1, 79066, 1387, 1, 0, 0 +37099,Jackson County, NC, North Carolina, 2, -5:1, 30210, 1387, 1, 0, 0 +08077,Mesa County, CO, Colorado, 8, -7:1, 112891, 4647, 1, 0, 0 +31067,Gage County, NE, Nebraska, 6, -6:1, 22666, 1148, 1, 0, 0 +48323,Maverick County, TX, Texas, 7, -6:1, 48131, 1148, 1, 0, 0 +26053,Gogebic County, MI, Michigan, 4, -5:1, 17097, 1387, 1, 0, 0 +17145,Perry County, IL, Illinois, 6, -6:1, 21048, 1148, 1, 0, 0 +47165,Sumner County, TN, Tennesee, 3, -6:1, 124056, 1148, 1, 0, 0 +35051,Sierra County, NM, New Mexico, 8, -7:1, 11025, 4647, 1, 0, 0 +20077,Harper County, KS, Kansas, 6, -6:1, 6430, 1148, 1, 0, 0 +50009,Essex County, VT, Vermont, 0, -5:1, 6580, 1387, 1, 0, 0 +18001,Adams County, IN, Indiana, 4, -5:1, 33083, 1387, 1, 0, 0 +40057,Harmon County, OK, Oklahoma, 7, -6:1, 3479, 1148, 1, 0, 0 +06013,Contra Costa County, CA, California, 9, -8:1, 918200, 3240, 0, 1, 0 +21005,Anderson County, KY, Kentucky, 4, -6:1, 18587, 1148, 1, 0, 0 +31005,Arthur County, NE, Nebraska, 6, -6:1, 428, 1148, 1, 0, 0 +26145,Saginaw County, MI, Michigan, 4, -5:1, 210101, 1387, 1, 0, 0 +48361,Orange County, TX, Texas, 7, -6:1, 84905, 1148, 1, 0, 0 +13273,Terrell County, GA, Georgia, 3, -5:1, 11146, 1387, 1, 0, 0 +40029,Coal County, OK, Oklahoma, 7, -6:1, 6009, 1148, 1, 0, 0 +27057,Hubbard County, MN, Minnesota, 5, -6:1, 16935, 1148, 1, 0, 0 +19007,Appanoose County, IA, Iowa, 5, -6:1, 13595, 1148, 1, 0, 0 +48445,Terry County, TX, Texas, 7, -6:1, 12896, 1148, 1, 0, 0 +34033,Salem County, NJ, New Jersey, 0, -5:1, 64912, 1387, 1, 0, 0 +20125,Montgomery County, KS, Kansas, 6, -6:1, 37089, 1148, 1, 0, 0 +35037,Quay County, NM, New Mexico, 8, -7:1, 10024, 4647, 1, 0, 0 +13083,Dade County, GA, Georgia, 3, -5:1, 15058, 1387, 1, 0, 0 +48041,Brazos County, TX, Texas, 7, -6:1, 133407, 1148, 1, 0, 0 +48255,Karnes County, TX, Texas, 7, -6:1, 12358, 1148, 1, 0, 0 +30095,Stillwater County, MT, Montana, 6, -7:1, 8069, 4647, 1, 0, 0 +21199,Pulaski County, KY, Kentucky, 4, -5:1, 56294, 1387, 1, 0, 0 +06043,Mariposa County, CA, California, 9, -8:1, 15877, 3240, 0, 1, 0 +55047,Green Lake County, WI, Wisconnsin, 5, -6:1, 19438, 1148, 1, 0, 0 +22125,West Feliciana Parish, LA, Louisiana, 7, -6:1, 13446, 1148, 1, 0, 0 +24005,Baltimore County, MD, Maryland, 2, -5:1, 721874, 1387, 1, 0, 0 +47029,Cocke County, TN, Tennesee, 3, -5:1, 31968, 1387, 1, 0, 0 +48383,Reagan County, TX, Texas, 7, -6:1, 4203, 1148, 1, 0, 0 +37083,Halifax County, NC, North Carolina, 2, -5:1, 56433, 1387, 1, 0, 0 +45001,Abbeville County, SC, South Carolina, 2, -5:1, 24632, 1387, 1, 0, 0 +51181,Surry County, VA, Virginia, 2, -5:1, 6471, 1387, 1, 0, 0 +51111,Lunenburg County, VA, Virginia, 2, -5:1, 12043, 1387, 1, 0, 0 +27117,Pipestone County, MN, Minnesota, 5, -6:1, 10092, 1148, 1, 0, 0 +06103,Tehama County, CA, California, 9, -8:1, 54073, 3240, 0, 1, 0 +48071,Chambers County, TX, Texas, 7, -6:1, 23743, 1148, 1, 0, 0 +35025,Lea County, NM, New Mexico, 8, -7:1, 56091, 4647, 1, 0, 0 +13319,Wilkinson County, GA, Georgia, 3, -5:1, 10838, 1387, 1, 0, 0 +13201,Miller County, GA, Georgia, 3, -5:1, 6409, 1387, 1, 0, 0 +26159,Van Buren County, MI, Michigan, 4, -5:1, 75666, 1387, 1, 0, 0 +18171,Warren County, IN, Indiana, 4, -5:1, 8251, 1387, 1, 0, 0 +40019,Carter County, OK, Oklahoma, 7, -6:1, 44503, 1148, 1, 0, 0 +31129,Nuckolls County, NE, Nebraska, 6, -7:1, 5226, 4647, 1, 0, 0 +41035,Klamath County, OR, Oregon, 9, -8:1, 63185, 3240, 0, 1, 0 +24039,Somerset County, MD, Maryland, 2, -5:1, 24296, 1387, 1, 0, 0 +13039,Camden County, GA, Georgia, 3, -5:1, 47443, 1387, 1, 0, 0 +22081,Red River Parish, LA, Louisiana, 7, -6:1, 9599, 1148, 1, 0, 0 +35021,Harding County, NM, New Mexico, 8, -7:1, 899, 4647, 1, 0, 0 +20067,Grant County, KS, Kansas, 6, -6:1, 8012, 1148, 1, 0, 0 +51089,Henry County, VA, Virginia, 2, -5:1, 55627, 1387, 1, 0, 0 +40105,Nowata County, OK, Oklahoma, 7, -6:1, 9969, 1148, 1, 0, 0 +31065,Furnas County, NE, Nebraska, 6, -6:1, 5381, 1148, 1, 0, 0 +13213,Murray County, GA, Georgia, 3, -5:1, 32682, 1387, 1, 0, 0 +55103,Richland County, WI, Wisconnsin, 5, -6:1, 17891, 1148, 1, 0, 0 +38049,McHenry County, ND, North Dakota, 5, -6:1, 6076, 1148, 1, 0, 0 +55063,La Crosse County, WI, Wisconnsin, 5, -6:1, 102565, 1148, 1, 0, 0 +55099,Price County, WI, Wisconnsin, 5, -6:1, 15813, 1148, 1, 0, 0 +49031,Piute County, UT, Utah, 8, -7:1, 1402, 4647, 1, 0, 0 +20057,Ford County, KS, Kansas, 6, -6:1, 29382, 1148, 1, 0, 0 +28057,Itawamba County, MS, Missippi, 5, -6:1, 21072, 1148, 1, 0, 0 +16051,Jefferson County, ID, Idaho, 8, -7:1, 19118, 4647, 1, 0, 0 +01035,Conecuh County, AL, Alabama, 3, -6:1, 13976, 1148, 1, 0, 0 +31111,Lincoln County, NE, Nebraska, 6, -7:1, 33515, 4647, 1, 0, 0 +51187,Warren County, VA, Virginia, 2, -5:1, 30126, 1387, 1, 0, 0 +28049,Hinds County, MS, Missippi, 5, -6:1, 247144, 1148, 1, 0, 0 +48237,Jack County, TX, Texas, 7, -6:1, 7430, 1148, 1, 0, 0 +31013,Box Butte County, NE, Nebraska, 6, -6:1, 12832, 1148, 1, 0, 0 +08003,Alamosa County, CO, Colorado, 8, -7:1, 14448, 4647, 1, 0, 0 +54055,Mercer County, WV, West Virginia, 2, -5:1, 63794, 1387, 1, 0, 0 +55013,Burnett County, WI, Wisconnsin, 5, -6:1, 14646, 1148, 1, 0, 0 +27143,Sibley County, MN, Minnesota, 5, -6:1, 14573, 1148, 1, 0, 0 +18127,Porter County, IN, Indiana, 4, -5:1, 145726, 1387, 1, 0, 0 +02068,Denali Borough, AK, Alaska, 9, -9:1, 1970, 1174, 0, 0, 1 +45075,Orangeburg County, SC, South Carolina, 2, -5:1, 87865, 1387, 1, 0, 0 +21003,Allen County, KY, Kentucky, 4, -6:1, 16555, 1148, 1, 0, 0 +30003,Big Horn County, MT, Montana, 6, -7:1, 12631, 4647, 1, 0, 0 +08029,Delta County, CO, Colorado, 8, -7:1, 26619, 4647, 1, 0, 0 +18109,Morgan County, IN, Indiana, 4, -5:1, 65500, 1387, 1, 0, 0 +21043,Carter County, KY, Kentucky, 4, -6:1, 26848, 1148, 1, 0, 0 +01123,Tallapoosa County, AL, Alabama, 3, -6:1, 40606, 1148, 1, 0, 0 +09013,Tolland County, CT, Connecticut, 0, -5:1, 131831, 1387, 1, 0, 0 +39127,Perry County, OH, Ohio, 4, -5:1, 34290, 1387, 1, 0, 0 +42059,Greene County, PA, Pennsylvania, 1, -5:1, 40742, 1387, 1, 0, 0 +20165,Rush County, KS, Kansas, 6, -6:1, 3413, 1148, 1, 0, 0 +30027,Fergus County, MT, Montana, 6, -7:1, 12271, 4647, 1, 0, 0 +46021,Campbell County, SD, South Dakota, 5, -6:1, 1917, 1148, 1, 0, 0 +13249,Schley County, GA, Georgia, 3, -5:1, 3945, 1387, 1, 0, 0 +51179,Stafford County, VA, Virginia, 2, -5:1, 87055, 1387, 1, 0, 0 +01107,Pickens County, AL, Alabama, 3, -6:1, 21089, 1148, 1, 0, 0 +36027,Dutchess County, NY, New York, 1, -5:1, 265317, 1387, 1, 0, 0 +31017,Brown County, NE, Nebraska, 6, -6:1, 3553, 1148, 1, 0, 0 +36109,Tompkins County, NY, New York, 1, -5:1, 96020, 1387, 1, 0, 0 +39143,Sandusky County, OH, Ohio, 4, -5:1, 62216, 1387, 1, 0, 0 +13117,Forsyth County, GA, Georgia, 3, -5:1, 86130, 1387, 1, 0, 0 +02070,Dillingham Census Area, AK, Alaska, 9, -9:1, 4534, 1174, 0, 0, 1 +51011,Appomattox County, VA, Virginia, 2, -5:1, 13134, 1387, 1, 0, 0 +51640,Galax city, VA, Virginia, 2, -5:1, 6864, 1387, 1, 0, 0 +17097,Lake County, IL, Illinois, 6, -6:1, 605116, 1148, 1, 0, 0 +17093,Kendall County, IL, Illinois, 6, -6:1, 51817, 1148, 1, 0, 0 +37093,Hoke County, NC, North Carolina, 2, -5:1, 30424, 1387, 1, 0, 0 +53031,Jefferson County, WA, Washiington, 9, -8:1, 26232, 3240, 0, 1, 0 +33001,Belknap County, NH, New Hampshire, 0, -5:1, 52481, 1387, 1, 0, 0 +48465,Val Verde County, TX, Texas, 7, -6:1, 43831, 1148, 1, 0, 0 +16055,Kootenai County, ID, Idaho, 8, -7:1, 101390, 4647, 1, 0, 0 +05003,Ashley County, AR, Arkansas, 7, -6:1, 24448, 1148, 1, 0, 0 +39063,Hancock County, OH, Ohio, 4, -5:1, 68922, 1387, 1, 0, 0 +39165,Warren County, OH, Ohio, 4, -5:1, 146033, 1387, 1, 0, 0 +42091,Montgomery County, PA, Pennsylvania, 1, -5:1, 719718, 1387, 1, 0, 0 +41065,Wasco County, OR, Oregon, 9, -8:1, 23059, 3240, 0, 1, 0 +28101,Newton County, MS, Missippi, 5, -6:1, 21516, 1148, 1, 0, 0 +48169,Garza County, TX, Texas, 7, -6:1, 4608, 1148, 1, 0, 0 +17153,Pulaski County, IL, Illinois, 6, -6:1, 7203, 1148, 1, 0, 0 +13153,Houston County, GA, Georgia, 3, -5:1, 105808, 1387, 1, 0, 0 +13063,Clayton County, GA, Georgia, 3, -5:1, 208999, 1387, 1, 0, 0 +30065,Musselshell County, MT, Montana, 6, -7:1, 4605, 4647, 1, 0, 0 +06035,Lassen County, CA, California, 9, -8:1, 33285, 3240, 0, 1, 0 +24043,Washington County, MD, Maryland, 2, -5:1, 127352, 1387, 1, 0, 0 +46009,Bon Homme County, SD, South Dakota, 5, -6:1, 7696, 1148, 1, 0, 0 +55059,Kenosha County, WI, Wisconnsin, 5, -6:1, 144339, 1148, 1, 0, 0 +01003,Baldwin County, AL, Alabama, 3, -6:1, 132828, 1148, 1, 0, 0 +23013,Knox County, ME, Maine, 0, -5:1, 37847, 1387, 1, 0, 0 +31147,Richardson County, NE, Nebraska, 6, -7:1, 9420, 4647, 1, 0, 0 +08047,Gilpin County, CO, Colorado, 8, -7:1, 4188, 4647, 1, 0, 0 +28119,Quitman County, MS, Missippi, 5, -6:1, 9914, 1148, 1, 0, 0 +47033,Crockett County, TN, Tennesee, 3, -5:1, 13959, 1387, 1, 0, 0 +26117,Montcalm County, MI, Michigan, 4, -5:1, 60559, 1387, 1, 0, 0 +25023,Plymouth County, MA, Massachusetts, 0, -5:1, 467588, 1387, 1, 0, 0 +04015,Mohave County, AZ, Arizona, 8, -7:1, 130618, 4647, 1, 0, 0 +54059,Mingo County, WV, West Virginia, 2, -5:1, 31926, 1387, 1, 0, 0 +17033,Crawford County, IL, Illinois, 6, -6:1, 20954, 1148, 1, 0, 0 +37069,Franklin County, NC, North Carolina, 2, -5:1, 44743, 1387, 1, 0, 0 +40129,Roger Mills County, OK, Oklahoma, 7, -6:1, 3580, 1148, 1, 0, 0 +27125,Red Lake County, MN, Minnesota, 5, -6:1, 4270, 1148, 1, 0, 0 +17035,Cumberland County, IL, Illinois, 6, -6:1, 11124, 1148, 1, 0, 0 +27051,Grant County, MN, Minnesota, 5, -6:1, 6178, 1148, 1, 0, 0 +13053,Chattahoochee County, GA, Georgia, 3, -5:1, 16679, 1387, 1, 0, 0 +27149,Stevens County, MN, Minnesota, 5, -6:1, 10136, 1148, 1, 0, 0 +17079,Jasper County, IL, Illinois, 6, -6:1, 10647, 1148, 1, 0, 0 +31159,Seward County, NE, Nebraska, 6, -7:1, 16299, 4647, 1, 0, 0 +37159,Rowan County, NC, North Carolina, 2, -5:1, 125505, 1387, 1, 0, 0 +34021,Mercer County, NJ, New Jersey, 0, -5:1, 331629, 1387, 1, 0, 0 +47081,Hickman County, TN, Tennesee, 3, -5:1, 20553, 1387, 1, 0, 0 +55115,Shawano County, WI, Wisconnsin, 5, -6:1, 38756, 1148, 1, 0, 0 +46097,Miner County, SD, South Dakota, 5, -7:1, 2799, 4647, 1, 0, 0 +13075,Cook County, GA, Georgia, 3, -5:1, 15011, 1387, 1, 0, 0 +37021,Buncombe County, NC, North Carolina, 2, -5:1, 194873, 1387, 1, 0, 0 +21159,Martin County, KY, Kentucky, 4, -5:1, 12120, 1387, 1, 0, 0 +36007,Broome County, NY, New York, 1, -5:1, 196545, 1387, 1, 0, 0 +04021,Pinal County, AZ, Arizona, 8, -7:1, 146929, 4647, 1, 0, 0 +29165,Platte County, MO, Mosourri, 6, -6:1, 70068, 1148, 1, 0, 0 +12105,Polk County, FL, Florida, 3, -5:1, 452584, 1387, 1, 0, 0 +37151,Randolph County, NC, North Carolina, 2, -5:1, 121289, 1387, 1, 0, 0 +51133,Northumberland County, VA, Virginia, 2, -5:1, 11513, 1387, 1, 0, 0 +19091,Humboldt County, IA, Iowa, 5, -6:1, 10323, 1148, 1, 0, 0 +20003,Anderson County, KS, Kansas, 6, -6:1, 8060, 1148, 1, 0, 0 +37179,Union County, NC, North Carolina, 2, -5:1, 110017, 1387, 1, 0, 0 +05035,Crittenden County, AR, Arkansas, 7, -6:1, 49905, 1148, 1, 0, 0 +20059,Franklin County, KS, Kansas, 6, -6:1, 24768, 1148, 1, 0, 0 +02100,Haines Borough, AK, Alaska, 9, -9:1, 2225, 1174, 0, 0, 1 +18181,White County, IN, Indiana, 4, -5:1, 25338, 1387, 1, 0, 0 +13207,Monroe County, GA, Georgia, 3, -5:1, 19645, 1387, 1, 0, 0 +42021,Cambria County, PA, Pennsylvania, 1, -5:1, 156080, 1387, 1, 0, 0 +29035,Carter County, MO, Mosourri, 6, -6:1, 6387, 1148, 1, 0, 0 +17149,Pike County, IL, Illinois, 6, -6:1, 17341, 1148, 1, 0, 0 +48015,Austin County, TX, Texas, 7, -6:1, 23439, 1148, 1, 0, 0 +22071,Orleans Parish, LA, Louisiana, 7, -6:1, 465538, 1148, 1, 0, 0 +40099,Murray County, OK, Oklahoma, 7, -6:1, 12335, 1148, 1, 0, 0 +21015,Boone County, KY, Kentucky, 4, -6:1, 79671, 1148, 1, 0, 0 +19061,Dubuque County, IA, Iowa, 5, -6:1, 87806, 1148, 1, 0, 0 +29125,Maries County, MO, Mosourri, 6, -6:1, 8473, 1148, 1, 0, 0 +51009,Amherst County, VA, Virginia, 2, -5:1, 30042, 1387, 1, 0, 0 +38045,LaMoure County, ND, North Dakota, 5, -6:1, 4759, 1148, 1, 0, 0 +16069,Nez Perce County, ID, Idaho, 8, -7:1, 36852, 4647, 1, 0, 0 +26025,Calhoun County, MI, Michigan, 4, -5:1, 141005, 1387, 1, 0, 0 +37027,Caldwell County, NC, North Carolina, 2, -5:1, 76096, 1387, 1, 0, 0 +02164,Lake and Peninsula Borough, AK, Alaska, 9, -9:1, 1699, 1174, 0, 0, 1 +16039,Elmore County, ID, Idaho, 8, -7:1, 25173, 4647, 1, 0, 0 +30085,Roosevelt County, MT, Montana, 6, -7:1, 10987, 4647, 1, 0, 0 +26011,Arenac County, MI, Michigan, 4, -5:1, 16413, 1387, 1, 0, 0 +13121,Fulton County, GA, Georgia, 3, -5:1, 739367, 1387, 1, 0, 0 +17051,Fayette County, IL, Illinois, 6, -6:1, 21972, 1148, 1, 0, 0 +29049,Clinton County, MO, Mosourri, 6, -6:1, 19070, 1148, 1, 0, 0 +21237,Wolfe County, KY, Kentucky, 4, -5:1, 7366, 1387, 1, 0, 0 +39103,Medina County, OH, Ohio, 4, -5:1, 144019, 1387, 1, 0, 0 +39043,Erie County, OH, Ohio, 4, -5:1, 78279, 1387, 1, 0, 0 +17087,Johnson County, IL, Illinois, 6, -6:1, 13283, 1148, 1, 0, 0 +18157,Tippecanoe County, IN, Indiana, 4, -5:1, 139005, 1387, 1, 0, 0 +16083,Twin Falls County, ID, Idaho, 8, -7:1, 62265, 4647, 1, 0, 0 +40149,Washita County, OK, Oklahoma, 7, -6:1, 11796, 1148, 1, 0, 0 +13233,Polk County, GA, Georgia, 3, -5:1, 36308, 1387, 1, 0, 0 +19015,Boone County, IA, Iowa, 5, -6:1, 26233, 1148, 1, 0, 0 +19125,Marion County, IA, Iowa, 5, -6:1, 31357, 1148, 1, 0, 0 +20171,Scott County, KS, Kansas, 6, -6:1, 5018, 1148, 1, 0, 0 +42053,Forest County, PA, Pennsylvania, 1, -5:1, 5002, 1387, 1, 0, 0 +48205,Hartley County, TX, Texas, 7, -6:1, 5102, 1148, 1, 0, 0 +20199,Wallace County, KS, Kansas, 6, -6:1, 1802, 1148, 1, 0, 0 +47145,Roane County, TN, Tennesee, 3, -6:1, 50026, 1148, 1, 0, 0 +29113,Lincoln County, MO, Mosourri, 6, -6:1, 36556, 1148, 1, 0, 0 +30009,Carbon County, MT, Montana, 6, -7:1, 9444, 4647, 1, 0, 0 +53049,Pacific County, WA, Washiington, 9, -8:1, 20802, 3240, 0, 1, 0 +01045,Dale County, AL, Alabama, 3, -6:1, 48872, 1148, 1, 0, 0 +51071,Giles County, VA, Virginia, 2, -5:1, 16242, 1387, 1, 0, 0 +06099,Stanislaus County, CA, California, 9, -8:1, 426460, 3240, 0, 1, 0 +28093,Marshall County, MS, Missippi, 5, -6:1, 32296, 1148, 1, 0, 0 +47049,Fentress County, TN, Tennesee, 3, -5:1, 16184, 1387, 1, 0, 0 +01095,Marshall County, AL, Alabama, 3, -6:1, 80346, 1148, 1, 0, 0 +25025,Suffolk County, MA, Massachusetts, 0, -5:1, 641715, 1387, 1, 0, 0 +13287,Turner County, GA, Georgia, 3, -5:1, 9160, 1387, 1, 0, 0 +51049,Cumberland County, VA, Virginia, 2, -5:1, 7851, 1387, 1, 0, 0 +11001,District of Columbia, DC, District of Columbia, 2, -5:1, 523124, 1387, 1, 0, 0 +20021,Cherokee County, KS, Kansas, 6, -6:1, 22552, 1148, 1, 0, 0 +54107,Wood County, WV, West Virginia, 2, -5:1, 86768, 1387, 1, 0, 0 +48103,Crane County, TX, Texas, 7, -6:1, 4510, 1148, 1, 0, 0 +21075,Fulton County, KY, Kentucky, 4, -6:1, 7542, 1148, 1, 0, 0 +42013,Blair County, PA, Pennsylvania, 1, -5:1, 130615, 1387, 1, 0, 0 +44005,Newport County, RI, Rhode Island, 0, -5:1, 82868, 1387, 1, 0, 0 +19105,Jones County, IA, Iowa, 5, -6:1, 20349, 1148, 1, 0, 0 +39135,Preble County, OH, Ohio, 4, -5:1, 43226, 1387, 1, 0, 0 +26031,Cheboygan County, MI, Michigan, 4, -5:1, 23738, 1387, 1, 0, 0 +18107,Montgomery County, IN, Indiana, 4, -5:1, 36337, 1387, 1, 0, 0 +37005,Alleghany County, NC, North Carolina, 2, -5:1, 9842, 1387, 1, 0, 0 +46129,Walworth County, SD, South Dakota, 5, -7:1, 5582, 4647, 1, 0, 0 +37165,Scotland County, NC, North Carolina, 2, -5:1, 35802, 1387, 1, 0, 0 +05149,Yell County, AR, Arkansas, 7, -6:1, 19110, 1148, 1, 0, 0 +21085,Grayson County, KY, Kentucky, 4, -6:1, 23763, 1148, 1, 0, 0 +55139,Winnebago County, WI, Wisconnsin, 5, -6:1, 149818, 1148, 1, 0, 0 +26057,Gratiot County, MI, Michigan, 4, -5:1, 40126, 1387, 1, 0, 0 +37141,Pender County, NC, North Carolina, 2, -5:1, 39510, 1387, 1, 0, 0 +41037,Lake County, OR, Oregon, 9, -8:1, 7152, 3240, 0, 1, 0 +48125,Dickens County, TX, Texas, 7, -6:1, 2242, 1148, 1, 0, 0 +38009,Bottineau County, ND, North Dakota, 5, -6:1, 7226, 1148, 1, 0, 0 +36067,Onondaga County, NY, New York, 1, -5:1, 458301, 1387, 1, 0, 0 +08061,Kiowa County, CO, Colorado, 8, -7:1, 1633, 4647, 1, 0, 0 +27067,Kandiyohi County, MN, Minnesota, 5, -6:1, 41086, 1148, 1, 0, 0 +53001,Adams County, WA, Washiington, 9, -8:1, 15324, 3240, 0, 1, 0 +35027,Lincoln County, NM, New Mexico, 8, -7:1, 16400, 4647, 1, 0, 0 +20051,Ellis County, KS, Kansas, 6, -6:1, 26309, 1148, 1, 0, 0 +31051,Dixon County, NE, Nebraska, 6, -6:1, 6300, 1148, 1, 0, 0 +21131,Leslie County, KY, Kentucky, 4, -5:1, 13582, 1387, 1, 0, 0 +13297,Walton County, GA, Georgia, 3, -5:1, 54485, 1387, 1, 0, 0 +28099,Neshoba County, MS, Missippi, 5, -6:1, 27653, 1148, 1, 0, 0 +31087,Hitchcock County, NE, Nebraska, 6, -6:1, 3442, 1148, 1, 0, 0 +20085,Jackson County, KS, Kansas, 6, -6:1, 12130, 1148, 1, 0, 0 +39131,Pike County, OH, Ohio, 4, -5:1, 27775, 1387, 1, 0, 0 +27005,Becker County, MN, Minnesota, 5, -6:1, 29381, 1148, 1, 0, 0 +20145,Pawnee County, KS, Kansas, 6, -6:1, 7437, 1148, 1, 0, 0 +17189,Washington County, IL, Illinois, 6, -6:1, 15367, 1148, 1, 0, 0 +01025,Clarke County, AL, Alabama, 3, -6:1, 28499, 1148, 1, 0, 0 +37101,Johnston County, NC, North Carolina, 2, -5:1, 106582, 1387, 1, 0, 0 +13171,Lamar County, GA, Georgia, 3, -5:1, 14706, 1387, 1, 0, 0 +18131,Pulaski County, IN, Indiana, 4, -5:1, 13257, 1387, 1, 0, 0 +17183,Vermilion County, IL, Illinois, 6, -6:1, 84204, 1148, 1, 0, 0 +45067,Marion County, SC, South Carolina, 2, -5:1, 34610, 1387, 1, 0, 0 +38021,Dickey County, ND, North Dakota, 5, -6:1, 5644, 1148, 1, 0, 0 +30063,Missoula County, MT, Montana, 6, -7:1, 88989, 4647, 1, 0, 0 +45091,York County, SC, South Carolina, 2, -5:1, 154313, 1387, 1, 0, 0 +20169,Saline County, KS, Kansas, 6, -6:1, 51617, 1148, 1, 0, 0 +22111,Union Parish, LA, Louisiana, 7, -6:1, 21989, 1148, 1, 0, 0 +48291,Liberty County, TX, Texas, 7, -6:1, 65078, 1148, 1, 0, 0 +47097,Lauderdale County, TN, Tennesee, 3, -6:1, 24206, 1148, 1, 0, 0 +42019,Butler County, PA, Pennsylvania, 1, -5:1, 170785, 1387, 1, 0, 0 +46041,Dewey County, SD, South Dakota, 5, -6:1, 5821, 1148, 1, 0, 0 +39067,Harrison County, OH, Ohio, 4, -5:1, 16097, 1387, 1, 0, 0 +48453,Travis County, TX, Texas, 7, -6:1, 710626, 1148, 1, 0, 0 +48443,Terrell County, TX, Texas, 7, -6:1, 1181, 1148, 1, 0, 0 +51540,Charlottesville city, VA, Virginia, 2, -5:1, 38223, 1387, 1, 0, 0 +30041,Hill County, MT, Montana, 6, -7:1, 17373, 4647, 1, 0, 0 +16045,Gem County, ID, Idaho, 8, -7:1, 14816, 4647, 1, 0, 0 +22047,Iberville Parish, LA, Louisiana, 7, -6:1, 31173, 1148, 1, 0, 0 +17095,Knox County, IL, Illinois, 6, -6:1, 55526, 1148, 1, 0, 0 +26061,Houghton County, MI, Michigan, 4, -5:1, 35719, 1387, 1, 0, 0 +37061,Duplin County, NC, North Carolina, 2, -5:1, 42993, 1387, 1, 0, 0 +23025,Somerset County, ME, Maine, 0, -5:1, 52380, 1387, 1, 0, 0 +20061,Geary County, KS, Kansas, 6, -6:1, 25370, 1148, 1, 0, 0 +36105,Sullivan County, NY, New York, 1, -5:1, 69111, 1387, 1, 0, 0 +21203,Rockcastle County, KY, Kentucky, 4, -5:1, 15951, 1387, 1, 0, 0 +20183,Smith County, KS, Kansas, 6, -6:1, 4588, 1148, 1, 0, 0 +42117,Tioga County, PA, Pennsylvania, 1, -5:1, 41606, 1387, 1, 0, 0 +29137,Monroe County, MO, Mosourri, 6, -6:1, 9021, 1148, 1, 0, 0 +42071,Lancaster County, PA, Pennsylvania, 1, -5:1, 456414, 1387, 1, 0, 0 +48395,Robertson County, TX, Texas, 7, -6:1, 15527, 1148, 1, 0, 0 +18089,Lake County, IN, Indiana, 4, -5:1, 478323, 1387, 1, 0, 0 +12089,Nassau County, FL, Florida, 3, -5:1, 55349, 1387, 1, 0, 0 +55069,Lincoln County, WI, Wisconnsin, 5, -6:1, 29727, 1148, 1, 0, 0 +16077,Power County, ID, Idaho, 8, -7:1, 8309, 4647, 1, 0, 0 +13149,Heard County, GA, Georgia, 3, -5:1, 10082, 1387, 1, 0, 0 +25011,Franklin County, MA, Massachusetts, 0, -5:1, 70597, 1387, 1, 0, 0 +31153,Sarpy County, NE, Nebraska, 6, -7:1, 120785, 4647, 1, 0, 0 +42041,Cumberland County, PA, Pennsylvania, 1, -5:1, 208634, 1387, 1, 0, 0 +28059,Jackson County, MS, Missippi, 5, -6:1, 130910, 1148, 1, 0, 0 +37145,Person County, NC, North Carolina, 2, -5:1, 33647, 1387, 1, 0, 0 +21169,Metcalfe County, KY, Kentucky, 4, -5:1, 9561, 1387, 1, 0, 0 +51087,Henrico County, VA, Virginia, 2, -5:1, 246052, 1387, 1, 0, 0 +20151,Pratt County, KS, Kansas, 6, -6:1, 9700, 1148, 1, 0, 0 +06109,Tuolumne County, CA, California, 9, -8:1, 53248, 3240, 0, 1, 0 +05111,Poinsett County, AR, Arkansas, 7, -6:1, 24750, 1148, 1, 0, 0 +30019,Daniels County, MT, Montana, 6, -7:1, 2001, 4647, 1, 0, 0 +39039,Defiance County, OH, Ohio, 4, -5:1, 39824, 1387, 1, 0, 0 +02282,Yakutat Borough, AK, Alaska, 9, -9:1, 799, 1174, 0, 0, 1 +22015,Bossier Parish, LA, Louisiana, 7, -6:1, 93463, 1148, 1, 0, 0 +29105,Laclede County, MO, Mosourri, 6, -6:1, 31029, 1148, 1, 0, 0 +12103,Pinellas County, FL, Florida, 3, -5:1, 878231, 1387, 1, 0, 0 +36011,Cayuga County, NY, New York, 1, -5:1, 81264, 1387, 1, 0, 0 +45073,Oconee County, SC, South Carolina, 2, -5:1, 64059, 1387, 1, 0, 0 +55041,Forest County, WI, Wisconnsin, 5, -6:1, 9645, 1148, 1, 0, 0 +29219,Warren County, MO, Mosourri, 6, -6:1, 24600, 1148, 1, 0, 0 +18129,Posey County, IN, Indiana, 4, -5:1, 26512, 1387, 1, 0, 0 +23003,Aroostook County, ME, Maine, 0, -5:1, 76085, 1387, 1, 0, 0 +48409,San Patricio County, TX, Texas, 7, -6:1, 71393, 1148, 1, 0, 0 +13057,Cherokee County, GA, Georgia, 3, -5:1, 134498, 1387, 1, 0, 0 +01131,Wilcox County, AL, Alabama, 3, -6:1, 13468, 1148, 1, 0, 0 +26013,Baraga County, MI, Michigan, 4, -5:1, 8413, 1387, 1, 0, 0 +05139,Union County, AR, Arkansas, 7, -6:1, 45304, 1148, 1, 0, 0 +28055,Issaquena County, MS, Missippi, 5, -6:1, 1629, 1148, 1, 0, 0 +29205,Shelby County, MO, Mosourri, 6, -6:1, 6802, 1148, 1, 0, 0 +31173,Thurston County, NE, Nebraska, 6, -7:1, 7181, 4647, 1, 0, 0 +51740,Portsmouth city, VA, Virginia, 2, -5:1, 98936, 1387, 1, 0, 0 +28145,Union County, MS, Missippi, 5, -6:1, 23828, 1148, 1, 0, 0 +22105,Tangipahoa Parish, LA, Louisiana, 7, -6:1, 96983, 1148, 1, 0, 0 +36029,Erie County, NY, New York, 1, -5:1, 934471, 1387, 1, 0, 0 +29211,Sullivan County, MO, Mosourri, 6, -6:1, 7040, 1148, 1, 0, 0 +31169,Thayer County, NE, Nebraska, 6, -7:1, 6277, 4647, 1, 0, 0 +19175,Union County, IA, Iowa, 5, -6:1, 12554, 1148, 1, 0, 0 +06101,Sutter County, CA, California, 9, -8:1, 76976, 3240, 0, 1, 0 +46057,Hamlin County, SD, South Dakota, 5, -7:1, 5335, 4647, 1, 0, 0 +47103,Lincoln County, TN, Tennesee, 3, -6:1, 29761, 1148, 1, 0, 0 +30011,Carter County, MT, Montana, 6, -7:1, 1537, 4647, 1, 0, 0 +20043,Doniphan County, KS, Kansas, 6, -6:1, 7856, 1148, 1, 0, 0 +32003,Clark County, NV, Nevada, 8, -8:1, 1162129, 3240, 0, 1, 0 +06075,San Francisco County, CA, California, 9, -8:1, 745774, 3240, 0, 1, 0 +18045,Fountain County, IN, Indiana, 4, -5:1, 18348, 1387, 1, 0, 0 +47127,Moore County, TN, Tennesee, 3, -6:1, 5196, 1148, 1, 0, 0 +21113,Jessamine County, KY, Kentucky, 4, -6:1, 36533, 1148, 1, 0, 0 +40139,Texas County, OK, Oklahoma, 7, -6:1, 18640, 1148, 1, 0, 0 +37177,Tyrrell County, NC, North Carolina, 2, -5:1, 3734, 1387, 1, 0, 0 +05069,Jefferson County, AR, Arkansas, 7, -6:1, 81556, 1148, 1, 0, 0 +55093,Pierce County, WI, Wisconnsin, 5, -6:1, 35606, 1148, 1, 0, 0 +53009,Clallam County, WA, Washiington, 9, -8:1, 64169, 3240, 0, 1, 0 +51015,Augusta County, VA, Virginia, 2, -5:1, 61775, 1387, 1, 0, 0 +06027,Inyo County, CA, California, 9, -8:1, 18125, 3240, 0, 1, 0 +18165,Vermillion County, IN, Indiana, 4, -5:1, 16908, 1387, 1, 0, 0 +19153,Polk County, IA, Iowa, 5, -6:1, 359826, 1148, 1, 0, 0 +19009,Audubon County, IA, Iowa, 5, -6:1, 6784, 1148, 1, 0, 0 +40051,Grady County, OK, Oklahoma, 7, -6:1, 45934, 1148, 1, 0, 0 +21025,Breathitt County, KY, Kentucky, 4, -6:1, 15686, 1148, 1, 0, 0 +17193,White County, IL, Illinois, 6, -6:1, 15646, 1148, 1, 0, 0 +08057,Jackson County, CO, Colorado, 8, -7:1, 1535, 4647, 1, 0, 0 +24015,Cecil County, MD, Maryland, 2, -5:1, 82522, 1387, 1, 0, 0 +48293,Limestone County, TX, Texas, 7, -6:1, 20930, 1148, 1, 0, 0 +19133,Monona County, IA, Iowa, 5, -6:1, 10110, 1148, 1, 0, 0 +48381,Randall County, TX, Texas, 7, -6:1, 99664, 1148, 1, 0, 0 +18021,Clay County, IN, Indiana, 4, -5:1, 26637, 1387, 1, 0, 0 +16003,Adams County, ID, Idaho, 8, -7:1, 3804, 4647, 1, 0, 0 +21209,Scott County, KY, Kentucky, 4, -5:1, 30685, 1387, 1, 0, 0 +51019,Bedford County, VA, Virginia, 2, -5:1, 55872, 1387, 1, 0, 0 +46063,Harding County, SD, South Dakota, 5, -7:1, 1476, 4647, 1, 0, 0 +55131,Washington County, WI, Wisconnsin, 5, -6:1, 113906, 1148, 1, 0, 0 +19067,Floyd County, IA, Iowa, 5, -6:1, 16353, 1148, 1, 0, 0 +18179,Wells County, IN, Indiana, 4, -5:1, 26842, 1387, 1, 0, 0 +38003,Barnes County, ND, North Dakota, 5, -6:1, 11958, 1148, 1, 0, 0 +13195,Madison County, GA, Georgia, 3, -5:1, 24312, 1387, 1, 0, 0 +05049,Fulton County, AR, Arkansas, 7, -6:1, 10901, 1148, 1, 0, 0 +47117,Marshall County, TN, Tennesee, 3, -6:1, 26302, 1148, 1, 0, 0 +47037,Davidson County, TN, Tennesee, 3, -5:1, 533967, 1387, 1, 0, 0 +18067,Howard County, IN, Indiana, 4, -5:1, 83452, 1387, 1, 0, 0 +06017,El Dorado County, CA, California, 9, -8:1, 158502, 3240, 0, 1, 0 +05067,Jackson County, AR, Arkansas, 7, -6:1, 17783, 1148, 1, 0, 0 +12117,Seminole County, FL, Florida, 3, -5:1, 350859, 1387, 1, 0, 0 +16071,Oneida County, ID, Idaho, 8, -7:1, 4051, 4647, 1, 0, 0 +26085,Lake County, MI, Michigan, 4, -5:1, 10475, 1387, 1, 0, 0 +41059,Umatilla County, OR, Oregon, 9, -8:1, 65495, 3240, 0, 1, 0 +13173,Lanier County, GA, Georgia, 3, -5:1, 6986, 1387, 1, 0, 0 +27141,Sherburne County, MN, Minnesota, 5, -6:1, 60391, 1148, 1, 0, 0 +13237,Putnam County, GA, Georgia, 3, -5:1, 17559, 1387, 1, 0, 0 +17073,Henry County, IL, Illinois, 6, -6:1, 51580, 1148, 1, 0, 0 +30075,Powder River County, MT, Montana, 6, -7:1, 1826, 4647, 1, 0, 0 +31121,Merrick County, NE, Nebraska, 6, -7:1, 8052, 4647, 1, 0, 0 +42017,Bucks County, PA, Pennsylvania, 1, -5:1, 587942, 1387, 1, 0, 0 +40131,Rogers County, OK, Oklahoma, 7, -6:1, 68128, 1148, 1, 0, 0 +19043,Clayton County, IA, Iowa, 5, -6:1, 18722, 1148, 1, 0, 0 +53075,Whitman County, WA, Washiington, 9, -8:1, 39487, 3240, 0, 1, 0 +05021,Clay County, AR, Arkansas, 7, -6:1, 17223, 1148, 1, 0, 0 +31119,Madison County, NE, Nebraska, 6, -7:1, 34585, 4647, 1, 0, 0 +16031,Cassia County, ID, Idaho, 8, -7:1, 21359, 4647, 1, 0, 0 +12113,Santa Rosa County, FL, Florida, 3, -5:1, 117322, 1387, 1, 0, 0 +48135,Ector County, TX, Texas, 7, -6:1, 125729, 1148, 1, 0, 0 +48461,Upton County, TX, Texas, 7, -6:1, 3749, 1148, 1, 0, 0 +36103,Suffolk County, NY, New York, 1, -5:1, 1371269, 1387, 1, 0, 0 +26163,Wayne County, MI, Michigan, 4, -5:1, 2118129, 1387, 1, 0, 0 +01071,Jackson County, AL, Alabama, 3, -6:1, 51329, 1148, 1, 0, 0 +21121,Knox County, KY, Kentucky, 4, -5:1, 31890, 1387, 1, 0, 0 +48467,Van Zandt County, TX, Texas, 7, -6:1, 44037, 1148, 1, 0, 0 +34019,Hunterdon County, NJ, New Jersey, 0, -5:1, 122428, 1387, 1, 0, 0 +27103,Nicollet County, MN, Minnesota, 5, -6:1, 29600, 1148, 1, 0, 0 +29103,Knox County, MO, Mosourri, 6, -6:1, 4355, 1148, 1, 0, 0 +41019,Douglas County, OR, Oregon, 9, -8:1, 101837, 3240, 0, 1, 0 +41069,Wheeler County, OR, Oregon, 9, -8:1, 1566, 3240, 0, 1, 0 +28025,Clay County, MS, Missippi, 5, -6:1, 21637, 1148, 1, 0, 0 +21163,Meade County, KY, Kentucky, 4, -5:1, 28809, 1387, 1, 0, 0 +21017,Bourbon County, KY, Kentucky, 4, -6:1, 19368, 1148, 1, 0, 0 +19189,Winnebago County, IA, Iowa, 5, -6:1, 11931, 1148, 1, 0, 0 +26071,Iron County, MI, Michigan, 4, -5:1, 12883, 1387, 1, 0, 0 +13181,Lincoln County, GA, Georgia, 3, -5:1, 8276, 1387, 1, 0, 0 +29003,Andrew County, MO, Mosourri, 6, -6:1, 15562, 1148, 1, 0, 0 +13113,Fayette County, GA, Georgia, 3, -5:1, 88609, 1387, 1, 0, 0 +45007,Anderson County, SC, South Carolina, 2, -5:1, 160791, 1387, 1, 0, 0 +25003,Berkshire County, MA, Massachusetts, 0, -5:1, 133038, 1387, 1, 0, 0 +12049,Hardee County, FL, Florida, 3, -5:1, 21046, 1387, 1, 0, 0 +02290,Yukon-Koyukuk Census Area, AK, Alaska, 9, -9:1, 5952, 1174, 0, 0, 1 +06031,Kings County, CA, California, 9, -8:1, 118866, 3240, 0, 1, 0 +27107,Norman County, MN, Minnesota, 5, -6:1, 7535, 1148, 1, 0, 0 +36065,Oneida County, NY, New York, 1, -5:1, 230628, 1387, 1, 0, 0 +30047,Lake County, MT, Montana, 6, -7:1, 25648, 4647, 1, 0, 0 +02122,Kenai Peninsula Borough, AK, Alaska, 9, -9:1, 48008, 1174, 0, 0, 1 +48289,Leon County, TX, Texas, 7, -6:1, 14489, 1148, 1, 0, 0 +40001,Adair County, OK, Oklahoma, 7, -6:1, 20349, 1148, 1, 0, 0 +21221,Trigg County, KY, Kentucky, 4, -5:1, 12399, 1387, 1, 0, 0 +45061,Lee County, SC, South Carolina, 2, -5:1, 20399, 1387, 1, 0, 0 +22029,Concordia Parish, LA, Louisiana, 7, -6:1, 20749, 1148, 1, 0, 0 +12073,Leon County, FL, Florida, 3, -5:1, 216978, 1387, 1, 0, 0 +26021,Berrien County, MI, Michigan, 4, -5:1, 160245, 1387, 1, 0, 0 +20103,Leavenworth County, KS, Kansas, 6, -6:1, 71299, 1148, 1, 0, 0 +22109,Terrebonne Parish, LA, Louisiana, 7, -6:1, 104534, 1148, 1, 0, 0 +19097,Jackson County, IA, Iowa, 5, -6:1, 20078, 1148, 1, 0, 0 +49045,Tooele County, UT, Utah, 8, -7:1, 33351, 4647, 1, 0, 0 +48387,Red River County, TX, Texas, 7, -6:1, 13731, 1148, 1, 0, 0 +13103,Effingham County, GA, Georgia, 3, -5:1, 36483, 1387, 1, 0, 0 +13125,Glascock County, GA, Georgia, 3, -5:1, 2512, 1387, 1, 0, 0 +18075,Jay County, IN, Indiana, 4, -5:1, 21729, 1387, 1, 0, 0 +45025,Chesterfield County, SC, South Carolina, 2, -5:1, 41080, 1387, 1, 0, 0 +19145,Page County, IA, Iowa, 5, -6:1, 17269, 1148, 1, 0, 0 +21053,Clinton County, KY, Kentucky, 4, -6:1, 9346, 1148, 1, 0, 0 +29065,Dent County, MO, Mosourri, 6, -6:1, 14103, 1148, 1, 0, 0 +05141,Van Buren County, AR, Arkansas, 7, -6:1, 15550, 1148, 1, 0, 0 +02188,Northwest Arctic Borough, AK, Alaska, 9, -9:1, 6758, 1174, 0, 0, 1 +01057,Fayette County, AL, Alabama, 3, -6:1, 18133, 1148, 1, 0, 0 +54091,Taylor County, WV, West Virginia, 2, -5:1, 15326, 1387, 1, 0, 0 +13037,Calhoun County, GA, Georgia, 3, -5:1, 5053, 1387, 1, 0, 0 +39085,Lake County, OH, Ohio, 4, -5:1, 223779, 1387, 1, 0, 0 +19077,Guthrie County, IA, Iowa, 5, -6:1, 11571, 1148, 1, 0, 0 +17041,Douglas County, IL, Illinois, 6, -6:1, 19915, 1148, 1, 0, 0 +27041,Douglas County, MN, Minnesota, 5, -6:1, 31045, 1148, 1, 0, 0 +31131,Otoe County, NE, Nebraska, 6, -7:1, 14787, 4647, 1, 0, 0 +47045,Dyer County, TN, Tennesee, 3, -5:1, 36782, 1387, 1, 0, 0 +28007,Attala County, MS, Missippi, 5, -6:1, 18404, 1148, 1, 0, 0 +39083,Knox County, OH, Ohio, 4, -5:1, 53309, 1387, 1, 0, 0 +29225,Webster County, MO, Mosourri, 6, -6:1, 29108, 1148, 1, 0, 0 +54079,Putnam County, WV, West Virginia, 2, -5:1, 51164, 1387, 1, 0, 0 +01119,Sumter County, AL, Alabama, 3, -6:1, 15766, 1148, 1, 0, 0 +24041,Talbot County, MD, Maryland, 2, -5:1, 33065, 1387, 1, 0, 0 +54019,Fayette County, WV, West Virginia, 2, -5:1, 47930, 1387, 1, 0, 0 +54069,Ohio County, WV, West Virginia, 2, -5:1, 48287, 1387, 1, 0, 0 +12029,Dixie County, FL, Florida, 3, -5:1, 12959, 1387, 1, 0, 0 +13315,Wilcox County, GA, Georgia, 3, -5:1, 7365, 1387, 1, 0, 0 +13193,Macon County, GA, Georgia, 3, -5:1, 13244, 1387, 1, 0, 0 +26135,Oscoda County, MI, Michigan, 4, -5:1, 8882, 1387, 1, 0, 0 +50021,Rutland County, VT, Vermont, 0, -5:1, 62524, 1387, 1, 0, 0 +26137,Otsego County, MI, Michigan, 4, -5:1, 22129, 1387, 1, 0, 0 +38051,McIntosh County, ND, North Dakota, 5, -6:1, 3442, 1148, 1, 0, 0 +27165,Watonwan County, MN, Minnesota, 5, -6:1, 11470, 1148, 1, 0, 0 +47137,Pickett County, TN, Tennesee, 3, -6:1, 4629, 1148, 1, 0, 0 +18121,Parke County, IN, Indiana, 4, -5:1, 16720, 1387, 1, 0, 0 +49001,Beaver County, UT, Utah, 8, -7:1, 5896, 4647, 1, 0, 0 +48417,Shackelford County, TX, Texas, 7, -6:1, 3303, 1148, 1, 0, 0 +36073,Orleans County, NY, New York, 1, -5:1, 44518, 1387, 1, 0, 0 +01083,Limestone County, AL, Alabama, 3, -6:1, 62241, 1148, 1, 0, 0 +47083,Houston County, TN, Tennesee, 3, -5:1, 7853, 1387, 1, 0, 0 +37191,Wayne County, NC, North Carolina, 2, -5:1, 112227, 1387, 1, 0, 0 +29069,Dunklin County, MO, Mosourri, 6, -6:1, 32700, 1148, 1, 0, 0 +22051,Jefferson Parish, LA, Louisiana, 7, -6:1, 450933, 1148, 1, 0, 0 +08019,Clear Creek County, CO, Colorado, 8, -7:1, 9001, 4647, 1, 0, 0 +26127,Oceana County, MI, Michigan, 4, -5:1, 24833, 1387, 1, 0, 0 +46135,Yankton County, SD, South Dakota, 5, -6:1, 21051, 1148, 1, 0, 0 +20023,Cheyenne County, KS, Kansas, 6, -6:1, 3174, 1148, 1, 0, 0 +32029,Storey County, NV, Nevada, 8, -8:1, 3053, 3240, 0, 1, 0 +36115,Washington County, NY, New York, 1, -5:1, 60481, 1387, 1, 0, 0 +05125,Saline County, AR, Arkansas, 7, -6:1, 77412, 1148, 1, 0, 0 +51021,Bland County, VA, Virginia, 2, -5:1, 6748, 1387, 1, 0, 0 +48475,Ward County, TX, Texas, 7, -6:1, 11801, 1148, 1, 0, 0 +28015,Carroll County, MS, Missippi, 5, -6:1, 9995, 1148, 1, 0, 0 +48439,Tarrant County, TX, Texas, 7, -6:1, 1355273, 1148, 1, 0, 0 +25027,Worcester County, MA, Massachusetts, 0, -5:1, 731881, 1387, 1, 0, 0 +13285,Troup County, GA, Georgia, 3, -5:1, 58783, 1387, 1, 0, 0 +17177,Stephenson County, IL, Illinois, 6, -6:1, 48951, 1148, 1, 0, 0 +36083,Rensselaer County, NY, New York, 1, -5:1, 152689, 1387, 1, 0, 0 +29127,Marion County, MO, Mosourri, 6, -6:1, 27771, 1148, 1, 0, 0 +05013,Calhoun County, AR, Arkansas, 7, -6:1, 5729, 1148, 1, 0, 0 +29163,Pike County, MO, Mosourri, 6, -6:1, 16347, 1148, 1, 0, 0 +46035,Davison County, SD, South Dakota, 5, -6:1, 18006, 1148, 1, 0, 0 +48311,McMullen County, TX, Texas, 7, -6:1, 790, 1148, 1, 0, 0 +28151,Washington County, MS, Missippi, 5, -6:1, 65264, 1148, 1, 0, 0 +20063,Gove County, KS, Kansas, 6, -6:1, 3054, 1148, 1, 0, 0 +55135,Waupaca County, WI, Wisconnsin, 5, -6:1, 50545, 1148, 1, 0, 0 +05087,Madison County, AR, Arkansas, 7, -6:1, 13224, 1148, 1, 0, 0 +48363,Palo Pinto County, TX, Texas, 7, -6:1, 25756, 1148, 1, 0, 0 +25017,Middlesex County, MA, Massachusetts, 0, -5:1, 1424116, 1387, 1, 0, 0 +51131,Northampton County, VA, Virginia, 2, -5:1, 12709, 1387, 1, 0, 0 +13239,Quitman County, GA, Georgia, 3, -5:1, 2486, 1387, 1, 0, 0 +38101,Ward County, ND, North Dakota, 5, -6:1, 58678, 1148, 1, 0, 0 +47151,Scott County, TN, Tennesee, 3, -6:1, 20044, 1148, 1, 0, 0 +08105,Rio Grande County, CO, Colorado, 8, -7:1, 11453, 4647, 1, 0, 0 +21223,Trimble County, KY, Kentucky, 4, -5:1, 7621, 1387, 1, 0, 0 +48393,Roberts County, TX, Texas, 7, -6:1, 939, 1148, 1, 0, 0 +41063,Wallowa County, OR, Oregon, 9, -8:1, 7368, 3240, 0, 1, 0 +27123,Ramsey County, MN, Minnesota, 5, -6:1, 485636, 1148, 1, 0, 0 +21031,Butler County, KY, Kentucky, 4, -6:1, 11926, 1148, 1, 0, 0 +29171,Putnam County, MO, Mosourri, 6, -6:1, 4912, 1148, 1, 0, 0 +34011,Cumberland County, NJ, New Jersey, 0, -5:1, 140341, 1387, 1, 0, 0 +18125,Pike County, IN, Indiana, 4, -5:1, 12882, 1387, 1, 0, 0 +13279,Toombs County, GA, Georgia, 3, -5:1, 25828, 1387, 1, 0, 0 +01021,Chilton County, AL, Alabama, 3, -6:1, 36918, 1148, 1, 0, 0 +16041,Franklin County, ID, Idaho, 8, -7:1, 11106, 4647, 1, 0, 0 +49033,Rich County, UT, Utah, 8, -7:1, 1834, 4647, 1, 0, 0 +46125,Turner County, SD, South Dakota, 5, -7:1, 8631, 4647, 1, 0, 0 +04019,Pima County, AZ, Arizona, 8, -7:1, 790755, 4647, 1, 0, 0 +36091,Saratoga County, NY, New York, 1, -5:1, 197606, 1387, 1, 0, 0 +46081,Lawrence County, SD, South Dakota, 5, -7:1, 22509, 4647, 1, 0, 0 +10003,New Castle County, DE, Delaware, 1, -5:1, 482807, 1387, 1, 0, 0 +05061,Howard County, AR, Arkansas, 7, -6:1, 13724, 1148, 1, 0, 0 +21087,Green County, KY, Kentucky, 4, -6:1, 10650, 1148, 1, 0, 0 +13071,Colquitt County, GA, Georgia, 3, -5:1, 40156, 1387, 1, 0, 0 +12033,Escambia County, FL, Florida, 3, -5:1, 282303, 1387, 1, 0, 0 +51139,Page County, VA, Virginia, 2, -5:1, 22989, 1387, 1, 0, 0 +17167,Sangamon County, IL, Illinois, 6, -6:1, 191378, 1148, 1, 0, 0 +17133,Monroe County, IL, Illinois, 6, -6:1, 26586, 1148, 1, 0, 0 +06061,Placer County, CA, California, 9, -8:1, 229259, 3240, 0, 1, 0 +30071,Phillips County, MT, Montana, 6, -7:1, 4821, 4647, 1, 0, 0 +04007,Gila County, AZ, Arizona, 8, -7:1, 48974, 4647, 1, 0, 0 +01133,Winston County, AL, Alabama, 3, -6:1, 24157, 1148, 1, 0, 0 +13073,Columbia County, GA, Georgia, 3, -5:1, 91118, 1387, 1, 0, 0 +27109,Olmsted County, MN, Minnesota, 5, -6:1, 116702, 1148, 1, 0, 0 +40083,Logan County, OK, Oklahoma, 7, -6:1, 30970, 1148, 1, 0, 0 +55031,Douglas County, WI, Wisconnsin, 5, -6:1, 43033, 1148, 1, 0, 0 +34025,Monmouth County, NJ, New Jersey, 0, -5:1, 603434, 1387, 1, 0, 0 +17091,Kankakee County, IL, Illinois, 6, -6:1, 102107, 1148, 1, 0, 0 +48347,Nacogdoches County, TX, Texas, 7, -6:1, 56220, 1148, 1, 0, 0 +23005,Cumberland County, ME, Maine, 0, -5:1, 253582, 1387, 1, 0, 0 +18047,Franklin County, IN, Indiana, 4, -5:1, 21808, 1387, 1, 0, 0 +01075,Lamar County, AL, Alabama, 3, -6:1, 15731, 1148, 1, 0, 0 +22063,Livingston Parish, LA, Louisiana, 7, -6:1, 88104, 1148, 1, 0, 0 +18177,Wayne County, IN, Indiana, 4, -5:1, 71313, 1387, 1, 0, 0 +28157,Wilkinson County, MS, Missippi, 5, -6:1, 9174, 1148, 1, 0, 0 +48413,Schleicher County, TX, Texas, 7, -6:1, 2984, 1148, 1, 0, 0 +22123,West Carroll Parish, LA, Louisiana, 7, -6:1, 12213, 1148, 1, 0, 0 +08091,Ouray County, CO, Colorado, 8, -7:1, 3313, 4647, 1, 0, 0 +51830,Williamsburg city, VA, Virginia, 2, -5:1, 11971, 1387, 1, 0, 0 +51153,Prince William County, VA, Virginia, 2, -5:1, 259827, 1387, 1, 0, 0 +47183,Weakley County, TN, Tennesee, 3, -6:1, 32942, 1148, 1, 0, 0 +48207,Haskell County, TX, Texas, 7, -6:1, 6158, 1148, 1, 0, 0 +38031,Foster County, ND, North Dakota, 5, -6:1, 3802, 1148, 1, 0, 0 +37073,Gates County, NC, North Carolina, 2, -5:1, 10070, 1387, 1, 0, 0 +01113,Russell County, AL, Alabama, 3, -6:1, 50387, 1148, 1, 0, 0 +27037,Dakota County, MN, Minnesota, 5, -6:1, 342528, 1148, 1, 0, 0 +37077,Granville County, NC, North Carolina, 2, -5:1, 42908, 1387, 1, 0, 0 +47023,Chester County, TN, Tennesee, 3, -5:1, 14700, 1387, 1, 0, 0 +28041,Greene County, MS, Missippi, 5, -6:1, 11766, 1148, 1, 0, 0 +17015,Carroll County, IL, Illinois, 6, -6:1, 16941, 1148, 1, 0, 0 +39133,Portage County, OH, Ohio, 4, -5:1, 151222, 1387, 1, 0, 0 +06047,Merced County, CA, California, 9, -8:1, 197730, 3240, 0, 1, 0 +20073,Greenwood County, KS, Kansas, 6, -6:1, 8139, 1148, 1, 0, 0 +17057,Fulton County, IL, Illinois, 6, -6:1, 38746, 1148, 1, 0, 0 +36095,Schoharie County, NY, New York, 1, -5:1, 32438, 1387, 1, 0, 0 +40013,Bryan County, OK, Oklahoma, 7, -6:1, 34690, 1148, 1, 0, 0 +17065,Hamilton County, IL, Illinois, 6, -6:1, 8611, 1148, 1, 0, 0 +28095,Monroe County, MS, Missippi, 5, -6:1, 38263, 1148, 1, 0, 0 +48083,Coleman County, TX, Texas, 7, -6:1, 9541, 1148, 1, 0, 0 +26039,Crawford County, MI, Michigan, 4, -5:1, 14150, 1387, 1, 0, 0 +27089,Marshall County, MN, Minnesota, 5, -6:1, 10313, 1148, 1, 0, 0 +51033,Caroline County, VA, Virginia, 2, -5:1, 22053, 1387, 1, 0, 0 +21239,Woodford County, KY, Kentucky, 4, -6:1, 22830, 1148, 1, 0, 0 +29123,Madison County, MO, Mosourri, 6, -6:1, 11481, 1148, 1, 0, 0 +48141,El Paso County, TX, Texas, 7, -6:1, 703127, 1148, 1, 0, 0 +29115,Linn County, MO, Mosourri, 6, -6:1, 13808, 1148, 1, 0, 0 +13191,McIntosh County, GA, Georgia, 3, -5:1, 10018, 1387, 1, 0, 0 +21091,Hancock County, KY, Kentucky, 4, -6:1, 8941, 1148, 1, 0, 0 +28051,Holmes County, MS, Missippi, 5, -6:1, 21522, 1148, 1, 0, 0 +19179,Wapello County, IA, Iowa, 5, -6:1, 35440, 1148, 1, 0, 0 +37153,Richmond County, NC, North Carolina, 2, -5:1, 46221, 1387, 1, 0, 0 +37127,Nash County, NC, North Carolina, 2, -5:1, 90968, 1387, 1, 0, 0 +19027,Carroll County, IA, Iowa, 5, -6:1, 21706, 1148, 1, 0, 0 +21211,Shelby County, KY, Kentucky, 4, -5:1, 29583, 1387, 1, 0, 0 +22009,Avoyelles Parish, LA, Louisiana, 7, -6:1, 40846, 1148, 1, 0, 0 +48107,Crosby County, TX, Texas, 7, -6:1, 7215, 1148, 1, 0, 0 +40023,Choctaw County, OK, Oklahoma, 7, -6:1, 15077, 1148, 1, 0, 0 +28153,Wayne County, MS, Missippi, 5, -6:1, 20368, 1148, 1, 0, 0 +16043,Fremont County, ID, Idaho, 8, -7:1, 11897, 4647, 1, 0, 0 +39045,Fairfield County, OH, Ohio, 4, -5:1, 123998, 1387, 1, 0, 0 +30037,Golden Valley County, MT, Montana, 6, -7:1, 1041, 4647, 1, 0, 0 +36023,Cortland County, NY, New York, 1, -5:1, 48033, 1387, 1, 0, 0 +56013,Fremont County, WY, Wyoming, 8, -7:1, 36044, 4647, 1, 0, 0 +29179,Reynolds County, MO, Mosourri, 6, -6:1, 6624, 1148, 1, 0, 0 +13197,Marion County, GA, Georgia, 3, -5:1, 6712, 1387, 1, 0, 0 +13031,Bulloch County, GA, Georgia, 3, -5:1, 50614, 1387, 1, 0, 0 +49011,Davis County, UT, Utah, 8, -7:1, 233013, 4647, 1, 0, 0 +16085,Valley County, ID, Idaho, 8, -7:1, 8005, 4647, 1, 0, 0 +15001,Hawaii County, HI, Hawaii, 9, -10:1, 143135, 6750, 0, 0, 1 +01089,Madison County, AL, Alabama, 3, -6:1, 278187, 1148, 1, 0, 0 +27061,Itasca County, MN, Minnesota, 5, -6:1, 43857, 1148, 1, 0, 0 +16073,Owyhee County, ID, Idaho, 8, -7:1, 10277, 4647, 1, 0, 0 +51167,Russell County, VA, Virginia, 2, -5:1, 29049, 1387, 1, 0, 0 +36069,Ontario County, NY, New York, 1, -5:1, 99662, 1387, 1, 0, 0 +13005,Bacon County, GA, Georgia, 3, -5:1, 10375, 1387, 1, 0, 0 +13069,Coffee County, GA, Georgia, 3, -5:1, 34298, 1387, 1, 0, 0 +12045,Gulf County, FL, Florida, 3, -5:1, 13476, 1387, 1, 0, 0 +19019,Buchanan County, IA, Iowa, 5, -6:1, 21190, 1148, 1, 0, 0 +47101,Lewis County, TN, Tennesee, 3, -6:1, 10868, 1148, 1, 0, 0 +08069,Larimer County, CO, Colorado, 8, -7:1, 231221, 4647, 1, 0, 0 +29157,Perry County, MO, Mosourri, 6, -6:1, 17410, 1148, 1, 0, 0 +51690,Martinsville city, VA, Virginia, 2, -5:1, 15668, 1387, 1, 0, 0 +48321,Matagorda County, TX, Texas, 7, -6:1, 37965, 1148, 1, 0, 0 +13027,Brooks County, GA, Georgia, 3, -5:1, 16000, 1387, 1, 0, 0 +39095,Lucas County, OH, Ohio, 4, -5:1, 448542, 1387, 1, 0, 0 +31091,Hooker County, NE, Nebraska, 6, -7:1, 702, 4647, 1, 0, 0 +56043,Washakie County, WY, Wyoming, 8, -7:1, 8669, 4647, 1, 0, 0 +12133,Washington County, FL, Florida, 3, -5:1, 20292, 1387, 1, 0, 0 +16047,Gooding County, ID, Idaho, 8, -7:1, 13626, 4647, 1, 0, 0 +41021,Gilliam County, OR, Oregon, 9, -8:1, 2023, 3240, 0, 1, 0 +46029,Codington County, SD, South Dakota, 5, -6:1, 25456, 1148, 1, 0, 0 +40145,Wagoner County, OK, Oklahoma, 7, -6:1, 55259, 1148, 1, 0, 0 +55113,Sawyer County, WI, Wisconnsin, 5, -6:1, 16110, 1148, 1, 0, 0 +41049,Morrow County, OR, Oregon, 9, -8:1, 9985, 3240, 0, 1, 0 +48233,Hutchinson County, TX, Texas, 7, -6:1, 24077, 1148, 1, 0, 0 +21179,Nelson County, KY, Kentucky, 4, -5:1, 35884, 1387, 1, 0, 0 +30067,Park County, MT, Montana, 6, -7:1, 15829, 4647, 1, 0, 0 +20095,Kingman County, KS, Kansas, 6, -6:1, 8543, 1148, 1, 0, 0 +21039,Carlisle County, KY, Kentucky, 4, -6:1, 5320, 1148, 1, 0, 0 +51660,Harrisonburg city, VA, Virginia, 2, -5:1, 33434, 1387, 1, 0, 0 +46043,Douglas County, SD, South Dakota, 5, -6:1, 3553, 1148, 1, 0, 0 +21145,McCracken County, KY, Kentucky, 4, -5:1, 64460, 1387, 1, 0, 0 +17161,Rock Island County, IL, Illinois, 6, -6:1, 147642, 1148, 1, 0, 0 +48419,Shelby County, TX, Texas, 7, -6:1, 22748, 1148, 1, 0, 0 +28019,Choctaw County, MS, Missippi, 5, -6:1, 9385, 1148, 1, 0, 0 +28137,Tate County, MS, Missippi, 5, -6:1, 23923, 1148, 1, 0, 0 +45077,Pickens County, SC, South Carolina, 2, -5:1, 107087, 1387, 1, 0, 0 +37129,New Hanover County, NC, North Carolina, 2, -5:1, 149832, 1387, 1, 0, 0 +13007,Baker County, GA, Georgia, 3, -5:1, 3673, 1387, 1, 0, 0 +29129,Mercer County, MO, Mosourri, 6, -6:1, 4003, 1148, 1, 0, 0 +19115,Louisa County, IA, Iowa, 5, -6:1, 11938, 1148, 1, 0, 0 +46071,Jackson County, SD, South Dakota, 5, -7:1, 2904, 4647, 1, 0, 0 +31089,Holt County, NE, Nebraska, 6, -7:1, 12042, 4647, 1, 0, 0 +51109,Louisa County, VA, Virginia, 2, -5:1, 24675, 1387, 1, 0, 0 +49021,Iron County, UT, Utah, 8, -7:1, 28659, 4647, 1, 0, 0 +21107,Hopkins County, KY, Kentucky, 4, -6:1, 46364, 1148, 1, 0, 0 +22075,Plaquemines Parish, LA, Louisiana, 7, -6:1, 26293, 1148, 1, 0, 0 +19103,Johnson County, IA, Iowa, 5, -6:1, 102724, 1148, 1, 0, 0 +39023,Clark County, OH, Ohio, 4, -5:1, 145341, 1387, 1, 0, 0 +19111,Lee County, IA, Iowa, 5, -6:1, 38471, 1148, 1, 0, 0 +06059,Orange County, CA, California, 9, -8:1, 2721701, 3240, 0, 1, 0 +22003,Allen Parish, LA, Louisiana, 7, -6:1, 23888, 1148, 1, 0, 0 +19045,Clinton County, IA, Iowa, 5, -6:1, 49897, 1148, 1, 0, 0 +12043,Glades County, FL, Florida, 3, -5:1, 8492, 1387, 1, 0, 0 +51155,Pulaski County, VA, Virginia, 2, -5:1, 34539, 1387, 1, 0, 0 +56001,Albany County, WY, Wyoming, 8, -7:1, 29185, 4647, 1, 0, 0 +29227,Worth County, MO, Mosourri, 6, -6:1, 2295, 1148, 1, 0, 0 +08073,Lincoln County, CO, Colorado, 8, -7:1, 5729, 4647, 1, 0, 0 +51005,Alleghany County, VA, Virginia, 2, -5:1, 12146, 1387, 1, 0, 0 +36049,Lewis County, NY, New York, 1, -5:1, 27494, 1387, 1, 0, 0 +48221,Hood County, TX, Texas, 7, -6:1, 37194, 1148, 1, 0, 0 +51125,Nelson County, VA, Virginia, 2, -5:1, 13917, 1387, 1, 0, 0 +12007,Bradford County, FL, Florida, 3, -5:1, 24777, 1387, 1, 0, 0 +31099,Kearney County, NE, Nebraska, 6, -7:1, 6853, 4647, 1, 0, 0 +48421,Sherman County, TX, Texas, 7, -6:1, 2864, 1148, 1, 0, 0 +48085,Collin County, TX, Texas, 7, -6:1, 428803, 1148, 1, 0, 0 +08083,Montezuma County, CO, Colorado, 8, -7:1, 22465, 4647, 1, 0, 0 +51165,Rockingham County, VA, Virginia, 2, -5:1, 63214, 1387, 1, 0, 0 +47003,Bedford County, TN, Tennesee, 3, -5:1, 34533, 1387, 1, 0, 0 +12125,Union County, FL, Florida, 3, -5:1, 12423, 1387, 1, 0, 0 +51590,Danville city, VA, Virginia, 2, -5:1, 50868, 1387, 1, 0, 0 +21103,Henry County, KY, Kentucky, 4, -6:1, 14765, 1148, 1, 0, 0 +37067,Forsyth County, NC, North Carolina, 2, -5:1, 287701, 1387, 1, 0, 0 +06085,Santa Clara County, CA, California, 9, -8:1, 1641215, 3240, 0, 1, 0 +48423,Smith County, TX, Texas, 7, -6:1, 168783, 1148, 1, 0, 0 +49003,Box Elder County, UT, Utah, 8, -7:1, 41949, 4647, 1, 0, 0 +48407,San Jacinto County, TX, Texas, 7, -6:1, 21768, 1148, 1, 0, 0 +42055,Franklin County, PA, Pennsylvania, 1, -5:1, 128002, 1387, 1, 0, 0 +47099,Lawrence County, TN, Tennesee, 3, -6:1, 39358, 1148, 1, 0, 0 +41067,Washington County, OR, Oregon, 9, -8:1, 399697, 3240, 0, 1, 0 +40137,Stephens County, OK, Oklahoma, 7, -6:1, 43410, 1148, 1, 0, 0 +17029,Coles County, IL, Illinois, 6, -6:1, 51103, 1148, 1, 0, 0 +24009,Calvert County, MD, Maryland, 2, -5:1, 71877, 1387, 1, 0, 0 +26121,Muskegon County, MI, Michigan, 4, -5:1, 166748, 1387, 1, 0, 0 +50001,Addison County, VT, Vermont, 0, -5:1, 35168, 1387, 1, 0, 0 +55141,Wood County, WI, Wisconnsin, 5, -6:1, 76036, 1148, 1, 0, 0 +49055,Wayne County, UT, Utah, 8, -7:1, 2379, 4647, 1, 0, 0 +17067,Hancock County, IL, Illinois, 6, -6:1, 21088, 1148, 1, 0, 0 +46011,Brookings County, SD, South Dakota, 5, -6:1, 25989, 1148, 1, 0, 0 +37183,Wake County, NC, North Carolina, 2, -5:1, 570615, 1387, 1, 0, 0 +37035,Catawba County, NC, North Carolina, 2, -5:1, 132545, 1387, 1, 0, 0 +37131,Northampton County, NC, North Carolina, 2, -5:1, 21184, 1387, 1, 0, 0 +55137,Waushara County, WI, Wisconnsin, 5, -6:1, 21609, 1148, 1, 0, 0 +05083,Logan County, AR, Arkansas, 7, -6:1, 21173, 1148, 1, 0, 0 +34035,Somerset County, NJ, New Jersey, 0, -5:1, 282900, 1387, 1, 0, 0 +06115,Yuba County, CA, California, 9, -8:1, 60067, 3240, 0, 1, 0 +05031,Craighead County, AR, Arkansas, 7, -6:1, 77500, 1148, 1, 0, 0 +48033,Borden County, TX, Texas, 7, -6:1, 758, 1148, 1, 0, 0 +51069,Frederick County, VA, Virginia, 2, -5:1, 55229, 1387, 1, 0, 0 +06069,San Benito County, CA, California, 9, -8:1, 48774, 3240, 0, 1, 0 +55033,Dunn County, WI, Wisconnsin, 5, -6:1, 38977, 1148, 1, 0, 0 +19183,Washington County, IA, Iowa, 5, -6:1, 20967, 1148, 1, 0, 0 +49027,Millard County, UT, Utah, 8, -7:1, 12249, 4647, 1, 0, 0 +54095,Tyler County, WV, West Virginia, 2, -5:1, 9835, 1387, 1, 0, 0 +05091,Miller County, AR, Arkansas, 7, -6:1, 39857, 1148, 1, 0, 0 +48051,Burleson County, TX, Texas, 7, -6:1, 15652, 1148, 1, 0, 0 +13111,Fannin County, GA, Georgia, 3, -5:1, 18622, 1387, 1, 0, 0 +48249,Jim Wells County, TX, Texas, 7, -6:1, 40028, 1148, 1, 0, 0 +16025,Camas County, ID, Idaho, 8, -7:1, 846, 4647, 1, 0, 0 +31021,Burt County, NE, Nebraska, 6, -6:1, 7998, 1148, 1, 0, 0 +51147,Prince Edward County, VA, Virginia, 2, -5:1, 19028, 1387, 1, 0, 0 +53015,Cowlitz County, WA, Washiington, 9, -8:1, 91574, 3240, 0, 1, 0 +23007,Franklin County, ME, Maine, 0, -5:1, 28933, 1387, 1, 0, 0 +19177,Van Buren County, IA, Iowa, 5, -6:1, 7886, 1148, 1, 0, 0 +02090,Fairbanks North Star Borough, AK, Alaska, 9, -9:1, 84217, 1174, 0, 0, 1 +39161,Van Wert County, OH, Ohio, 4, -5:1, 30200, 1387, 1, 0, 0 +54063,Monroe County, WV, West Virginia, 2, -5:1, 13205, 1387, 1, 0, 0 +53005,Benton County, WA, Washiington, 9, -8:1, 136250, 3240, 0, 1, 0 +34023,Middlesex County, NJ, New Jersey, 0, -5:1, 716176, 1387, 1, 0, 0 +25009,Essex County, MA, Massachusetts, 0, -5:1, 698806, 1387, 1, 0, 0 +48325,Medina County, TX, Texas, 7, -6:1, 37685, 1148, 1, 0, 0 +13255,Spalding County, GA, Georgia, 3, -5:1, 57626, 1387, 1, 0, 0 +35029,Luna County, NM, New Mexico, 8, -7:1, 24070, 4647, 1, 0, 0 +51750,Radford city, VA, Virginia, 2, -5:1, 15734, 1387, 1, 0, 0 +27031,Cook County, MN, Minnesota, 5, -6:1, 4792, 1148, 1, 0, 0 +31055,Douglas County, NE, Nebraska, 6, -6:1, 443794, 1148, 1, 0, 0 +06089,Shasta County, CA, California, 9, -8:1, 164349, 3240, 0, 1, 0 +05103,Ouachita County, AR, Arkansas, 7, -6:1, 27921, 1148, 1, 0, 0 +36017,Chenango County, NY, New York, 1, -5:1, 51052, 1387, 1, 0, 0 +34015,Gloucester County, NJ, New Jersey, 0, -5:1, 247897, 1387, 1, 0, 0 +27071,Koochiching County, MN, Minnesota, 5, -6:1, 15538, 1148, 1, 0, 0 +55075,Marinette County, WI, Wisconnsin, 5, -6:1, 43033, 1148, 1, 0, 0 +48037,Bowie County, TX, Texas, 7, -6:1, 83509, 1148, 1, 0, 0 +36041,Hamilton County, NY, New York, 1, -5:1, 5193, 1387, 1, 0, 0 +29009,Barry County, MO, Mosourri, 6, -6:1, 33120, 1148, 1, 0, 0 +49037,San Juan County, UT, Utah, 8, -7:1, 13711, 4647, 1, 0, 0 +37007,Anson County, NC, North Carolina, 2, -5:1, 24354, 1387, 1, 0, 0 +46015,Brule County, SD, South Dakota, 5, -6:1, 5555, 1148, 1, 0, 0 +23023,Sagadahoc County, ME, Maine, 0, -5:1, 35779, 1387, 1, 0, 0 +48003,Andrews County, TX, Texas, 7, -6:1, 13976, 1148, 1, 0, 0 +17127,Massac County, IL, Illinois, 6, -6:1, 15584, 1148, 1, 0, 0 +21147,McCreary County, KY, Kentucky, 4, -5:1, 16659, 1387, 1, 0, 0 +48193,Hamilton County, TX, Texas, 7, -6:1, 7603, 1148, 1, 0, 0 +24019,Dorchester County, MD, Maryland, 2, -5:1, 29503, 1387, 1, 0, 0 +31027,Cedar County, NE, Nebraska, 6, -6:1, 9650, 1148, 1, 0, 0 +45065,McCormick County, SC, South Carolina, 2, -5:1, 9545, 1387, 1, 0, 0 +13093,Dooly County, GA, Georgia, 3, -5:1, 10388, 1387, 1, 0, 0 +54045,Logan County, WV, West Virginia, 2, -5:1, 41080, 1387, 1, 0, 0 +42057,Fulton County, PA, Pennsylvania, 1, -5:1, 14498, 1387, 1, 0, 0 +39171,Williams County, OH, Ohio, 4, -5:1, 38001, 1387, 1, 0, 0 +29007,Audrain County, MO, Mosourri, 6, -6:1, 23573, 1148, 1, 0, 0 +36123,Yates County, NY, New York, 1, -5:1, 24202, 1387, 1, 0, 0 +27085,McLeod County, MN, Minnesota, 5, -6:1, 34017, 1148, 1, 0, 0 +51570,Colonial Heights city, VA, Virginia, 2, -5:1, 16955, 1387, 1, 0, 0 +04013,Maricopa County, AZ, Arizona, 8, -7:1, 2784075, 4647, 1, 0, 0 +16013,Blaine County, ID, Idaho, 8, -7:1, 17200, 4647, 1, 0, 0 +08043,Fremont County, CO, Colorado, 8, -7:1, 43904, 4647, 1, 0, 0 +12077,Liberty County, FL, Florida, 3, -5:1, 6759, 1387, 1, 0, 0 +08125,Yuma County, CO, Colorado, 8, -7:1, 9389, 4647, 1, 0, 0 +42077,Lehigh County, PA, Pennsylvania, 1, -5:1, 299341, 1387, 1, 0, 0 +08071,Las Animas County, CO, Colorado, 8, -7:1, 14573, 4647, 1, 0, 0 +28063,Jefferson County, MS, Missippi, 5, -6:1, 8427, 1148, 1, 0, 0 +51800,Suffolk city, VA, Virginia, 2, -5:1, 62703, 1387, 1, 0, 0 +42111,Somerset County, PA, Pennsylvania, 1, -5:1, 80267, 1387, 1, 0, 0 +48035,Bosque County, TX, Texas, 7, -6:1, 16557, 1148, 1, 0, 0 +29031,Cape Girardeau County, MO, Mosourri, 6, -6:1, 66314, 1148, 1, 0, 0 +18163,Vanderburgh County, IN, Indiana, 4, -5:1, 168179, 1387, 1, 0, 0 +21049,Clark County, KY, Kentucky, 4, -6:1, 31978, 1148, 1, 0, 0 +48027,Bell County, TX, Texas, 7, -6:1, 223468, 1148, 1, 0, 0 +02060,Bristol Bay Borough, AK, Alaska, 9, -9:1, 1356, 1174, 0, 0, 1 +18111,Newton County, IN, Indiana, 4, -5:1, 14734, 1387, 1, 0, 0 +35001,Bernalillo County, NM, New Mexico, 8, -7:1, 525958, 4647, 1, 0, 0 +55095,Polk County, WI, Wisconnsin, 5, -6:1, 38786, 1148, 1, 0, 0 +25015,Hampshire County, MA, Massachusetts, 0, -5:1, 149384, 1387, 1, 0, 0 +40037,Creek County, OK, Oklahoma, 7, -6:1, 67142, 1148, 1, 0, 0 +54021,Gilmer County, WV, West Virginia, 2, -5:1, 7130, 1387, 1, 0, 0 +30029,Flathead County, MT, Montana, 6, -7:1, 71831, 4647, 1, 0, 0 +26113,Missaukee County, MI, Michigan, 4, -5:1, 13892, 1387, 1, 0, 0 +19163,Scott County, IA, Iowa, 5, -6:1, 158591, 1148, 1, 0, 0 +26059,Hillsdale County, MI, Michigan, 4, -5:1, 46614, 1387, 1, 0, 0 +19075,Grundy County, IA, Iowa, 5, -6:1, 12183, 1148, 1, 0, 0 +39153,Summit County, OH, Ohio, 4, -5:1, 537730, 1387, 1, 0, 0 +51183,Sussex County, VA, Virginia, 2, -5:1, 9925, 1387, 1, 0, 0 +51029,Buckingham County, VA, Virginia, 2, -5:1, 14639, 1387, 1, 0, 0 +53021,Franklin County, WA, Washiington, 9, -8:1, 46459, 3240, 0, 1, 0 +37111,McDowell County, NC, North Carolina, 2, -5:1, 40048, 1387, 1, 0, 0 +47011,Bradley County, TN, Tennesee, 3, -5:1, 83292, 1387, 1, 0, 0 +21027,Breckinridge County, KY, Kentucky, 4, -6:1, 17465, 1148, 1, 0, 0 +30039,Granite County, MT, Montana, 6, -7:1, 2667, 4647, 1, 0, 0 +13009,Baldwin County, GA, Georgia, 3, -5:1, 41968, 1387, 1, 0, 0 +13101,Echols County, GA, Georgia, 3, -5:1, 2401, 1387, 1, 0, 0 +21063,Elliott County, KY, Kentucky, 4, -6:1, 6602, 1148, 1, 0, 0 +27127,Redwood County, MN, Minnesota, 5, -6:1, 16489, 1148, 1, 0, 0 +30077,Powell County, MT, Montana, 6, -7:1, 7000, 4647, 1, 0, 0 +40047,Garfield County, OK, Oklahoma, 7, -6:1, 56859, 1148, 1, 0, 0 +56023,Lincoln County, WY, Wyoming, 8, -7:1, 13876, 4647, 1, 0, 0 +29037,Cass County, MO, Mosourri, 6, -6:1, 80520, 1148, 1, 0, 0 +01081,Lee County, AL, Alabama, 3, -6:1, 100444, 1148, 1, 0, 0 +06033,Lake County, CA, California, 9, -8:1, 55147, 3240, 0, 1, 0 +13021,Bibb County, GA, Georgia, 3, -5:1, 156086, 1387, 1, 0, 0 +27151,Swift County, MN, Minnesota, 5, -6:1, 10804, 1148, 1, 0, 0 +20049,Elk County, KS, Kansas, 6, -6:1, 3351, 1148, 1, 0, 0 +42109,Snyder County, PA, Pennsylvania, 1, -5:1, 38226, 1387, 1, 0, 0 +46115,Spink County, SD, South Dakota, 5, -7:1, 7572, 4647, 1, 0, 0 +20015,Butler County, KS, Kansas, 6, -6:1, 61932, 1148, 1, 0, 0 +06081,San Mateo County, CA, California, 9, -8:1, 700765, 3240, 0, 1, 0 +49051,Wasatch County, UT, Utah, 8, -7:1, 13267, 4647, 1, 0, 0 +16061,Lewis County, ID, Idaho, 8, -7:1, 4007, 4647, 1, 0, 0 +18173,Warrick County, IN, Indiana, 4, -5:1, 51609, 1387, 1, 0, 0 +36019,Clinton County, NY, New York, 1, -5:1, 79970, 1387, 1, 0, 0 +20155,Reno County, KS, Kansas, 6, -6:1, 63211, 1148, 1, 0, 0 +20007,Barber County, KS, Kansas, 6, -6:1, 5342, 1148, 1, 0, 0 +37049,Craven County, NC, North Carolina, 2, -5:1, 88129, 1387, 1, 0, 0 +23015,Lincoln County, ME, Maine, 0, -5:1, 31815, 1387, 1, 0, 0 +18059,Hancock County, IN, Indiana, 4, -5:1, 54524, 1387, 1, 0, 0 +39065,Hardin County, OH, Ohio, 4, -5:1, 31725, 1387, 1, 0, 0 +17017,Cass County, IL, Illinois, 6, -6:1, 13266, 1148, 1, 0, 0 +29207,Stoddard County, MO, Mosourri, 6, -6:1, 29623, 1148, 1, 0, 0 +46007,Bennett County, SD, South Dakota, 5, -6:1, 3389, 1148, 1, 0, 0 +29117,Livingston County, MO, Mosourri, 6, -6:1, 14151, 1148, 1, 0, 0 +13261,Sumter County, GA, Georgia, 3, -5:1, 31324, 1387, 1, 0, 0 +40049,Garvin County, OK, Oklahoma, 7, -6:1, 27044, 1148, 1, 0, 0 +32015,Lander County, NV, Nevada, 8, -8:1, 6987, 3240, 0, 1, 0 +36093,Schenectady County, NY, New York, 1, -5:1, 145530, 1387, 1, 0, 0 +12027,DeSoto County, FL, Florida, 3, -5:1, 24820, 1387, 1, 0, 0 +48059,Callahan County, TX, Texas, 7, -6:1, 12796, 1148, 1, 0, 0 +48401,Rusk County, TX, Texas, 7, -6:1, 45877, 1148, 1, 0, 0 +47073,Hawkins County, TN, Tennesee, 3, -5:1, 49719, 1387, 1, 0, 0 +30059,Meagher County, MT, Montana, 6, -7:1, 1797, 4647, 1, 0, 0 +05099,Nevada County, AR, Arkansas, 7, -6:1, 10034, 1148, 1, 0, 0 +13055,Chattooga County, GA, Georgia, 3, -5:1, 22813, 1387, 1, 0, 0 +18007,Benton County, IN, Indiana, 4, -5:1, 9725, 1387, 1, 0, 0 +47125,Montgomery County, TN, Tennesee, 3, -6:1, 127265, 1148, 1, 0, 0 +36101,Steuben County, NY, New York, 1, -5:1, 97950, 1387, 1, 0, 0 +44003,Kent County, RI, Rhode Island, 0, -5:1, 161811, 1387, 1, 0, 0 +18137,Ripley County, IN, Indiana, 4, -5:1, 27205, 1387, 1, 0, 0 +48001,Anderson County, TX, Texas, 7, -6:1, 52352, 1148, 1, 0, 0 +49019,Grand County, UT, Utah, 8, -7:1, 8068, 4647, 1, 0, 0 +32033,White Pine County, NV, Nevada, 8, -8:1, 10078, 3240, 0, 1, 0 +19147,Palo Alto County, IA, Iowa, 5, -6:1, 10017, 1148, 1, 0, 0 +23017,Oxford County, ME, Maine, 0, -5:1, 53673, 1387, 1, 0, 0 +05047,Franklin County, AR, Arkansas, 7, -6:1, 16932, 1148, 1, 0, 0 +37041,Chowan County, NC, North Carolina, 2, -5:1, 14191, 1387, 1, 0, 0 +55079,Milwaukee County, WI, Wisconnsin, 5, -6:1, 911713, 1148, 1, 0, 0 +56035,Sublette County, WY, Wyoming, 8, -7:1, 5738, 4647, 1, 0, 0 +29181,Ripley County, MO, Mosourri, 6, -6:1, 14072, 1148, 1, 0, 0 +30045,Judith Basin County, MT, Montana, 6, -7:1, 2294, 4647, 1, 0, 0 +01041,Crenshaw County, AL, Alabama, 3, -6:1, 13636, 1148, 1, 0, 0 +06105,Trinity County, CA, California, 9, -8:1, 13117, 3240, 0, 1, 0 +01101,Montgomery County, AL, Alabama, 3, -6:1, 217693, 1148, 1, 0, 0 +29169,Pulaski County, MO, Mosourri, 6, -6:1, 38507, 1148, 1, 0, 0 +40073,Kingfisher County, OK, Oklahoma, 7, -6:1, 13528, 1148, 1, 0, 0 +19095,Iowa County, IA, Iowa, 5, -6:1, 15550, 1148, 1, 0, 0 +05145,White County, AR, Arkansas, 7, -6:1, 64526, 1148, 1, 0, 0 +26075,Jackson County, MI, Michigan, 4, -5:1, 156157, 1387, 1, 0, 0 +13165,Jenkins County, GA, Georgia, 3, -5:1, 8447, 1387, 1, 0, 0 +24025,Harford County, MD, Maryland, 2, -5:1, 214668, 1387, 1, 0, 0 +47043,Dickson County, TN, Tennesee, 3, -5:1, 42254, 1387, 1, 0, 0 +28127,Simpson County, MS, Missippi, 5, -6:1, 25338, 1148, 1, 0, 0 +27001,Aitkin County, MN, Minnesota, 5, -6:1, 14152, 1148, 1, 0, 0 +04009,Graham County, AZ, Arizona, 8, -7:1, 31696, 4647, 1, 0, 0 +17049,Effingham County, IL, Illinois, 6, -6:1, 33504, 1148, 1, 0, 0 +35035,Otero County, NM, New Mexico, 8, -7:1, 54630, 4647, 1, 0, 0 +53033,King County, WA, Washiington, 9, -8:1, 1654876, 3240, 0, 1, 0 +29139,Montgomery County, MO, Mosourri, 6, -6:1, 12074, 1148, 1, 0, 0 +51550,Chesapeake city, VA, Virginia, 2, -5:1, 199564, 1387, 1, 0, 0 +48459,Upshur County, TX, Texas, 7, -6:1, 35885, 1148, 1, 0, 0 +05113,Polk County, AR, Arkansas, 7, -6:1, 19662, 1148, 1, 0, 0 +02270,Wade Hampton Census Area, AK, Alaska, 9, -9:1, 6812, 1174, 0, 0, 1 +19005,Allamakee County, IA, Iowa, 5, -6:1, 13989, 1148, 1, 0, 0 +20039,Decatur County, KS, Kansas, 6, -6:1, 3456, 1148, 1, 0, 0 +55125,Vilas County, WI, Wisconnsin, 5, -6:1, 21277, 1148, 1, 0, 0 +51700,Newport News city, VA, Virginia, 2, -5:1, 178615, 1387, 1, 0, 0 +28129,Smith County, MS, Missippi, 5, -6:1, 15296, 1148, 1, 0, 0 +17169,Schuyler County, IL, Illinois, 6, -6:1, 7632, 1148, 1, 0, 0 +29053,Cooper County, MO, Mosourri, 6, -6:1, 16029, 1148, 1, 0, 0 +18005,Bartholomew County, IN, Indiana, 4, -5:1, 69579, 1387, 1, 0, 0 +48357,Ochiltree County, TX, Texas, 7, -6:1, 8827, 1148, 1, 0, 0 +20133,Neosho County, KS, Kansas, 6, -6:1, 16760, 1148, 1, 0, 0 +48287,Lee County, TX, Texas, 7, -6:1, 14916, 1148, 1, 0, 0 +05077,Lee County, AR, Arkansas, 7, -6:1, 12406, 1148, 1, 0, 0 +35011,DeBaca County, NM, New Mexico, 8, -7:1, 2389, 4647, 1, 0, 0 +45035,Dorchester County, SC, South Carolina, 2, -5:1, 88133, 1387, 1, 0, 0 +48373,Polk County, TX, Texas, 7, -6:1, 50309, 1148, 1, 0, 0 +33007,Coos County, NH, New Hampshire, 0, -5:1, 32875, 1387, 1, 0, 0 +37031,Carteret County, NC, North Carolina, 2, -5:1, 60054, 1387, 1, 0, 0 +54075,Pocahontas County, WV, West Virginia, 2, -5:1, 9268, 1387, 1, 0, 0 +16027,Canyon County, ID, Idaho, 8, -7:1, 120266, 4647, 1, 0, 0 +01039,Covington County, AL, Alabama, 3, -6:1, 37402, 1148, 1, 0, 0 +19041,Clay County, IA, Iowa, 5, -6:1, 17532, 1148, 1, 0, 0 +01033,Colbert County, AL, Alabama, 3, -6:1, 52946, 1148, 1, 0, 0 +48271,Kinney County, TX, Texas, 7, -6:1, 3482, 1148, 1, 0, 0 +54053,Mason County, WV, West Virginia, 2, -5:1, 25869, 1387, 1, 0, 0 +13077,Coweta County, GA, Georgia, 3, -5:1, 85028, 1387, 1, 0, 0 +18035,Delaware County, IN, Indiana, 4, -5:1, 116828, 1387, 1, 0, 0 +54017,Doddridge County, WV, West Virginia, 2, -5:1, 7554, 1387, 1, 0, 0 +24011,Caroline County, MD, Maryland, 2, -5:1, 29489, 1387, 1, 0, 0 +29199,Scotland County, MO, Mosourri, 6, -6:1, 4814, 1148, 1, 0, 0 +51075,Goochland County, VA, Virginia, 2, -5:1, 17823, 1387, 1, 0, 0 +01005,Barbour County, AL, Alabama, 3, -6:1, 26895, 1148, 1, 0, 0 +13189,McDuffie County, GA, Georgia, 3, -5:1, 21770, 1387, 1, 0, 0 +01055,Etowah County, AL, Alabama, 3, -6:1, 103975, 1148, 1, 0, 0 +20175,Seward County, KS, Kansas, 6, -6:1, 19984, 1148, 1, 0, 0 +48501,Yoakum County, TX, Texas, 7, -6:1, 8010, 1148, 1, 0, 0 +05039,Dallas County, AR, Arkansas, 7, -6:1, 9060, 1148, 1, 0, 0 +29081,Harrison County, MO, Mosourri, 6, -6:1, 8506, 1148, 1, 0, 0 +28105,Oktibbeha County, MS, Missippi, 5, -6:1, 39291, 1148, 1, 0, 0 +34007,Camden County, NJ, New Jersey, 0, -5:1, 505204, 1387, 1, 0, 0 +27147,Steele County, MN, Minnesota, 5, -6:1, 31736, 1148, 1, 0, 0 +55029,Door County, WI, Wisconnsin, 5, -6:1, 27027, 1148, 1, 0, 0 +18073,Jasper County, IN, Indiana, 4, -5:1, 29260, 1387, 1, 0, 0 +28155,Webster County, MS, Missippi, 5, -6:1, 10547, 1148, 1, 0, 0 +23019,Penobscot County, ME, Maine, 0, -5:1, 142323, 1387, 1, 0, 0 +13199,Meriwether County, GA, Georgia, 3, -5:1, 23112, 1387, 1, 0, 0 +37091,Hertford County, NC, North Carolina, 2, -5:1, 22289, 1387, 1, 0, 0 +02150,Kodiak Island Borough, AK, Alaska, 9, -9:1, 14520, 1174, 0, 0, 1 +48503,Young County, TX, Texas, 7, -6:1, 17697, 1148, 1, 0, 0 +47087,Jackson County, TN, Tennesee, 3, -5:1, 9629, 1387, 1, 0, 0 +29131,Miller County, MO, Mosourri, 6, -6:1, 22422, 1148, 1, 0, 0 +20161,Riley County, KS, Kansas, 6, -6:1, 63615, 1148, 1, 0, 0 +01105,Perry County, AL, Alabama, 3, -6:1, 12667, 1148, 1, 0, 0 +41007,Clatsop County, OR, Oregon, 9, -8:1, 35424, 3240, 0, 1, 0 +36013,Chautauqua County, NY, New York, 1, -5:1, 138103, 1387, 1, 0, 0 +18167,Vigo County, IN, Indiana, 4, -5:1, 105083, 1387, 1, 0, 0 +49009,Daggett County, UT, Utah, 8, -7:1, 737, 4647, 1, 0, 0 +19083,Hardin County, IA, Iowa, 5, -6:1, 18462, 1148, 1, 0, 0 +26131,Ontonagon County, MI, Michigan, 4, -5:1, 7878, 1387, 1, 0, 0 +20013,Brown County, KS, Kansas, 6, -6:1, 11070, 1148, 1, 0, 0 +48225,Houston County, TX, Texas, 7, -6:1, 21901, 1148, 1, 0, 0 +29021,Buchanan County, MO, Mosourri, 6, -6:1, 81776, 1148, 1, 0, 0 +48369,Parmer County, TX, Texas, 7, -6:1, 10302, 1148, 1, 0, 0 +31095,Jefferson County, NE, Nebraska, 6, -7:1, 8378, 4647, 1, 0, 0 +24031,Montgomery County, MD, Maryland, 2, -5:1, 840879, 1387, 1, 0, 0 +31133,Pawnee County, NE, Nebraska, 6, -7:1, 3131, 4647, 1, 0, 0 +17081,Jefferson County, IL, Illinois, 6, -6:1, 37373, 1148, 1, 0, 0 +42049,Erie County, PA, Pennsylvania, 1, -5:1, 276401, 1387, 1, 0, 0 +24013,Carroll County, MD, Maryland, 2, -5:1, 149697, 1387, 1, 0, 0 +48257,Kaufman County, TX, Texas, 7, -6:1, 65736, 1148, 1, 0, 0 +48391,Refugio County, TX, Texas, 7, -6:1, 7907, 1148, 1, 0, 0 +28133,Sunflower County, MS, Missippi, 5, -6:1, 34577, 1148, 1, 0, 0 +21177,Muhlenberg County, KY, Kentucky, 4, -5:1, 32173, 1387, 1, 0, 0 +26125,Oakland County, MI, Michigan, 4, -5:1, 1176488, 1387, 1, 0, 0 +41029,Jackson County, OR, Oregon, 9, -8:1, 173123, 3240, 0, 1, 0 +47069,Hardeman County, TN, Tennesee, 3, -5:1, 24895, 1387, 1, 0, 0 +54099,Wayne County, WV, West Virginia, 2, -5:1, 41957, 1387, 1, 0, 0 +47105,Loudon County, TN, Tennesee, 3, -6:1, 39052, 1148, 1, 0, 0 +54025,Greenbrier County, WV, West Virginia, 2, -5:1, 35383, 1387, 1, 0, 0 +54001,Barbour County, WV, West Virginia, 2, -5:1, 16152, 1387, 1, 0, 0 +12047,Hamilton County, FL, Florida, 3, -5:1, 12651, 1387, 1, 0, 0 +30089,Sanders County, MT, Montana, 6, -7:1, 10185, 4647, 1, 0, 0 +28033,DeSoto County, MS, Missippi, 5, -6:1, 96897, 1148, 1, 0, 0 +19195,Worth County, IA, Iowa, 5, -6:1, 7779, 1148, 1, 0, 0 +40127,Pushmataha County, OK, Oklahoma, 7, -6:1, 11584, 1148, 1, 0, 0 +38011,Bowman County, ND, North Dakota, 5, -6:1, 3317, 1148, 1, 0, 0 +05081,Little River County, AR, Arkansas, 7, -6:1, 13206, 1148, 1, 0, 0 +13035,Butts County, GA, Georgia, 3, -5:1, 17837, 1387, 1, 0, 0 +37053,Currituck County, NC, North Carolina, 2, -5:1, 17908, 1387, 1, 0, 0 +19057,Des Moines County, IA, Iowa, 5, -6:1, 41944, 1148, 1, 0, 0 +48251,Johnson County, TX, Texas, 7, -6:1, 118125, 1148, 1, 0, 0 +54089,Summers County, WV, West Virginia, 2, -5:1, 13146, 1387, 1, 0, 0 +48153,Floyd County, TX, Texas, 7, -6:1, 8191, 1148, 1, 0, 0 +37115,Madison County, NC, North Carolina, 2, -5:1, 18756, 1387, 1, 0, 0 +18041,Fayette County, IN, Indiana, 4, -5:1, 25969, 1387, 1, 0, 0 +21019,Boyd County, KY, Kentucky, 4, -6:1, 49543, 1148, 1, 0, 0 +48173,Glasscock County, TX, Texas, 7, -6:1, 1396, 1148, 1, 0, 0 +17089,Kane County, IL, Illinois, 6, -6:1, 391249, 1148, 1, 0, 0 +54039,Kanawha County, WV, West Virginia, 2, -5:1, 202011, 1387, 1, 0, 0 +13265,Taliaferro County, GA, Georgia, 3, -5:1, 1908, 1387, 1, 0, 0 +34027,Morris County, NJ, New Jersey, 0, -5:1, 459896, 1387, 1, 0, 0 +51105,Lee County, VA, Virginia, 2, -5:1, 23815, 1387, 1, 0, 0 +40079,Le Flore County, OK, Oklahoma, 7, -6:1, 46564, 1148, 1, 0, 0 +19063,Emmet County, IA, Iowa, 5, -6:1, 10887, 1148, 1, 0, 0 +48449,Titus County, TX, Texas, 7, -6:1, 25422, 1148, 1, 0, 0 +21069,Fleming County, KY, Kentucky, 4, -6:1, 13441, 1148, 1, 0, 0 +18017,Cass County, IN, Indiana, 4, -5:1, 38685, 1387, 1, 0, 0 +35053,Socorro County, NM, New Mexico, 8, -7:1, 16333, 4647, 1, 0, 0 +08001,Adams County, CO, Colorado, 8, -7:1, 323853, 4647, 1, 0, 0 +33019,Sullivan County, NH, New Hampshire, 0, -5:1, 40027, 1387, 1, 0, 0 +40039,Custer County, OK, Oklahoma, 7, -6:1, 25493, 1148, 1, 0, 0 +51081,Greensville County, VA, Virginia, 2, -5:1, 11281, 1387, 1, 0, 0 +48223,Hopkins County, TX, Texas, 7, -6:1, 30512, 1148, 1, 0, 0 +40011,Blaine County, OK, Oklahoma, 7, -6:1, 10513, 1148, 1, 0, 0 +36033,Franklin County, NY, New York, 1, -5:1, 48582, 1387, 1, 0, 0 +47039,Decatur County, TN, Tennesee, 3, -5:1, 10807, 1387, 1, 0, 0 +55127,Walworth County, WI, Wisconnsin, 5, -6:1, 85353, 1148, 1, 0, 0 +48217,Hill County, TX, Texas, 7, -6:1, 30534, 1148, 1, 0, 0 +13321,Worth County, GA, Georgia, 3, -5:1, 22485, 1387, 1, 0, 0 +21201,Robertson County, KY, Kentucky, 4, -5:1, 2209, 1387, 1, 0, 0 +47141,Putnam County, TN, Tennesee, 3, -6:1, 59143, 1148, 1, 0, 0 +21213,Simpson County, KY, Kentucky, 4, -5:1, 16401, 1387, 1, 0, 0 +40109,Oklahoma County, OK, Oklahoma, 7, -6:1, 632988, 1148, 1, 0, 0 +24035,Queen Anne County, MD, Maryland, 2, -5:1, 39672, 1387, 1, 0, 0 +17203,Woodford County, IL, Illinois, 6, -6:1, 35212, 1148, 1, 0, 0 +19127,Marshall County, IA, Iowa, 5, -6:1, 38732, 1148, 1, 0, 0 +35009,Curry County, NM, New Mexico, 8, -7:1, 45290, 4647, 1, 0, 0 +21009,Barren County, KY, Kentucky, 4, -6:1, 36979, 1148, 1, 0, 0 +21229,Washington County, KY, Kentucky, 4, -5:1, 10918, 1387, 1, 0, 0 +13183,Long County, GA, Georgia, 3, -5:1, 8585, 1387, 1, 0, 0 +22013,Bienville Parish, LA, Louisiana, 7, -6:1, 15814, 1148, 1, 0, 0 +31117,McPherson County, NE, Nebraska, 6, -7:1, 563, 4647, 1, 0, 0 +46055,Haakon County, SD, South Dakota, 5, -7:1, 2353, 4647, 1, 0, 0 +16005,Bannock County, ID, Idaho, 8, -7:1, 74866, 4647, 1, 0, 0 +04017,Navajo County, AZ, Arizona, 8, -7:1, 96997, 4647, 1, 0, 0 +37055,Dare County, NC, North Carolina, 2, -5:1, 28952, 1387, 1, 0, 0 +48067,Cass County, TX, Texas, 7, -6:1, 30828, 1148, 1, 0, 0 +17023,Clark County, IL, Illinois, 6, -6:1, 16534, 1148, 1, 0, 0 +55071,Manitowoc County, WI, Wisconnsin, 5, -6:1, 82412, 1148, 1, 0, 0 +16065,Madison County, ID, Idaho, 8, -7:1, 23569, 4647, 1, 0, 0 +31155,Saunders County, NE, Nebraska, 6, -7:1, 19245, 4647, 1, 0, 0 +35039,Rio Arriba County, NM, New Mexico, 8, -7:1, 37787, 4647, 1, 0, 0 +01125,Tuscaloosa County, AL, Alabama, 3, -6:1, 160768, 1148, 1, 0, 0 +16019,Bonneville County, ID, Idaho, 8, -7:1, 80672, 4647, 1, 0, 0 +55129,Washburn County, WI, Wisconnsin, 5, -6:1, 15421, 1148, 1, 0, 0 +16033,Clark County, ID, Idaho, 8, -7:1, 873, 4647, 1, 0, 0 +47115,Marion County, TN, Tennesee, 3, -6:1, 26851, 1148, 1, 0, 0 +37195,Wilson County, NC, North Carolina, 2, -5:1, 68188, 1387, 1, 0, 0 +21161,Mason County, KY, Kentucky, 4, -5:1, 17021, 1387, 1, 0, 0 +36051,Livingston County, NY, New York, 1, -5:1, 66000, 1387, 1, 0, 0 +13061,Clay County, GA, Georgia, 3, -5:1, 3453, 1387, 1, 0, 0 +20191,Sumner County, KS, Kansas, 6, -6:1, 27043, 1148, 1, 0, 0 +20143,Ottawa County, KS, Kansas, 6, -6:1, 5905, 1148, 1, 0, 0 +51127,New Kent County, VA, Virginia, 2, -5:1, 13052, 1387, 1, 0, 0 +19071,Fremont County, IA, Iowa, 5, -6:1, 7746, 1148, 1, 0, 0 +45059,Laurens County, SC, South Carolina, 2, -5:1, 63249, 1387, 1, 0, 0 +20117,Marshall County, KS, Kansas, 6, -6:1, 11006, 1148, 1, 0, 0 +06025,Imperial County, CA, California, 9, -8:1, 144051, 3240, 0, 1, 0 +27049,Goodhue County, MN, Minnesota, 5, -6:1, 43137, 1148, 1, 0, 0 +31105,Kimball County, NE, Nebraska, 6, -7:1, 4082, 4647, 1, 0, 0 +40043,Dewey County, OK, Oklahoma, 7, -6:1, 4928, 1148, 1, 0, 0 +29087,Holt County, MO, Mosourri, 6, -6:1, 5554, 1148, 1, 0, 0 +13159,Jasper County, GA, Georgia, 3, -5:1, 10155, 1387, 1, 0, 0 +48105,Crockett County, TX, Texas, 7, -6:1, 4602, 1148, 1, 0, 0 +54047,McDowell County, WV, West Virginia, 2, -5:1, 29916, 1387, 1, 0, 0 +48437,Swisher County, TX, Texas, 7, -6:1, 8301, 1148, 1, 0, 0 +38089,Stark County, ND, North Dakota, 5, -6:1, 22780, 1148, 1, 0, 0 +26051,Gladwin County, MI, Michigan, 4, -5:1, 25333, 1387, 1, 0, 0 +50027,Windsor County, VT, Vermont, 0, -5:1, 55444, 1387, 1, 0, 0 +46119,Sully County, SD, South Dakota, 5, -7:1, 1470, 4647, 1, 0, 0 +01109,Pike County, AL, Alabama, 3, -6:1, 28646, 1148, 1, 0, 0 +12059,Holmes County, FL, Florida, 3, -5:1, 18622, 1387, 1, 0, 0 +29145,Newton County, MO, Mosourri, 6, -6:1, 49152, 1148, 1, 0, 0 +37167,Stanly County, NC, North Carolina, 2, -5:1, 56083, 1387, 1, 0, 0 +53027,Grays Harbor County, WA, Washiington, 9, -8:1, 67739, 3240, 0, 1, 0 +24023,Garrett County, MD, Maryland, 2, -5:1, 29238, 1387, 1, 0, 0 +38097,Traill County, ND, North Dakota, 5, -6:1, 8544, 1148, 1, 0, 0 +18071,Jackson County, IN, Indiana, 4, -5:1, 40992, 1387, 1, 0, 0 +08085,Montrose County, CO, Colorado, 8, -7:1, 30764, 4647, 1, 0, 0 +38027,Eddy County, ND, North Dakota, 5, -6:1, 2847, 1148, 1, 0, 0 +29043,Christian County, MO, Mosourri, 6, -6:1, 48997, 1148, 1, 0, 0 +22127,Winn Parish, LA, Louisiana, 7, -6:1, 17714, 1148, 1, 0, 0 +39075,Holmes County, OH, Ohio, 4, -5:1, 37841, 1387, 1, 0, 0 +33005,Cheshire County, NH, New Hampshire, 0, -5:1, 71828, 1387, 1, 0, 0 +36053,Madison County, NY, New York, 1, -5:1, 71069, 1387, 1, 0, 0 +19079,Hamilton County, IA, Iowa, 5, -6:1, 16011, 1148, 1, 0, 0 +29001,Adair County, MO, Mosourri, 6, -6:1, 24286, 1148, 1, 0, 0 +48149,Fayette County, TX, Texas, 7, -6:1, 21414, 1148, 1, 0, 0 +42101,Philadelphia County, PA, Pennsylvania, 1, -5:1, 1436287, 1387, 1, 0, 0 +47129,Morgan County, TN, Tennesee, 3, -6:1, 18775, 1148, 1, 0, 0 +17137,Morgan County, IL, Illinois, 6, -6:1, 35346, 1148, 1, 0, 0 +17071,Henderson County, IL, Illinois, 6, -6:1, 8601, 1148, 1, 0, 0 +16009,Benewah County, ID, Idaho, 8, -7:1, 9119, 4647, 1, 0, 0 +48427,Starr County, TX, Texas, 7, -6:1, 55906, 1148, 1, 0, 0 +05131,Sebastian County, AR, Arkansas, 7, -6:1, 106180, 1148, 1, 0, 0 +38017,Cass County, ND, North Dakota, 5, -6:1, 116832, 1148, 1, 0, 0 +17135,Montgomery County, IL, Illinois, 6, -6:1, 31390, 1148, 1, 0, 0 +28143,Tunica County, MS, Missippi, 5, -6:1, 8039, 1148, 1, 0, 0 +47061,Grundy County, TN, Tennesee, 3, -5:1, 14138, 1387, 1, 0, 0 +56011,Crook County, WY, Wyoming, 8, -7:1, 5829, 4647, 1, 0, 0 +39159,Union County, OH, Ohio, 4, -5:1, 39494, 1387, 1, 0, 0 +21065,Estill County, KY, Kentucky, 4, -6:1, 15588, 1148, 1, 0, 0 +42007,Beaver County, PA, Pennsylvania, 1, -5:1, 184406, 1387, 1, 0, 0 +38025,Dunn County, ND, North Dakota, 5, -6:1, 3560, 1148, 1, 0, 0 +55083,Oconto County, WI, Wisconnsin, 5, -6:1, 34014, 1148, 1, 0, 0 +51820,Waynesboro city, VA, Virginia, 2, -5:1, 18561, 1387, 1, 0, 0 +18081,Johnson County, IN, Indiana, 4, -5:1, 109368, 1387, 1, 0, 0 +40005,Atoka County, OK, Oklahoma, 7, -6:1, 13237, 1148, 1, 0, 0 +48341,Moore County, TX, Texas, 7, -6:1, 19686, 1148, 1, 0, 0 +45053,Jasper County, SC, South Carolina, 2, -5:1, 16995, 1387, 1, 0, 0 +39101,Marion County, OH, Ohio, 4, -5:1, 64774, 1387, 1, 0, 0 +22027,Claiborne Parish, LA, Louisiana, 7, -6:1, 16919, 1148, 1, 0, 0 +13129,Gordon County, GA, Georgia, 3, -5:1, 41052, 1387, 1, 0, 0 +04001,Apache County, AZ, Arizona, 8, -7:1, 68782, 4647, 1, 0, 0 +48355,Nueces County, TX, Texas, 7, -6:1, 316340, 1148, 1, 0, 0 +18155,Switzerland County, IN, Indiana, 4, -5:1, 8893, 1387, 1, 0, 0 +41027,Hood River County, OR, Oregon, 9, -8:1, 19553, 3240, 0, 1, 0 +27035,Crow Wing County, MN, Minnesota, 5, -6:1, 51681, 1148, 1, 0, 0 +55065,Lafayette County, WI, Wisconnsin, 5, -6:1, 16261, 1148, 1, 0, 0 +21225,Union County, KY, Kentucky, 4, -5:1, 16577, 1387, 1, 0, 0 +06037,Los Angeles County, CA, California, 9, -8:1, 9213533, 3240, 0, 1, 0 +21219,Todd County, KY, Kentucky, 4, -5:1, 11222, 1387, 1, 0, 0 +39091,Logan County, OH, Ohio, 4, -5:1, 46204, 1387, 1, 0, 0 +26091,Lenawee County, MI, Michigan, 4, -5:1, 98412, 1387, 1, 0, 0 +46123,Tripp County, SD, South Dakota, 5, -7:1, 6737, 4647, 1, 0, 0 +40119,Payne County, OK, Oklahoma, 7, -6:1, 65109, 1148, 1, 0, 0 +55105,Rock County, WI, Wisconnsin, 5, -6:1, 150736, 1148, 1, 0, 0 +08081,Moffat County, CO, Colorado, 8, -7:1, 12535, 4647, 1, 0, 0 +48261,Kenedy County, TX, Texas, 7, -6:1, 438, 1148, 1, 0, 0 +51710,Norfolk city, VA, Virginia, 2, -5:1, 215215, 1387, 1, 0, 0 +29229,Wright County, MO, Mosourri, 6, -6:1, 19578, 1148, 1, 0, 0 +30107,Wheatland County, MT, Montana, 6, -7:1, 2373, 4647, 1, 0, 0 +37029,Camden County, NC, North Carolina, 2, -5:1, 6878, 1387, 1, 0, 0 +54027,Hampshire County, WV, West Virginia, 2, -5:1, 19041, 1387, 1, 0, 0 +17055,Franklin County, IL, Illinois, 6, -6:1, 40476, 1148, 1, 0, 0 +48441,Taylor County, TX, Texas, 7, -6:1, 122016, 1148, 1, 0, 0 +48203,Harrison County, TX, Texas, 7, -6:1, 59773, 1148, 1, 0, 0 +19049,Dallas County, IA, Iowa, 5, -6:1, 36900, 1148, 1, 0, 0 +44001,Bristol County, RI, Rhode Island, 0, -5:1, 49114, 1387, 1, 0, 0 +48181,Grayson County, TX, Texas, 7, -6:1, 102815, 1148, 1, 0, 0 +05119,Pulaski County, AR, Arkansas, 7, -6:1, 350345, 1148, 1, 0, 0 +48007,Aransas County, TX, Texas, 7, -6:1, 22910, 1148, 1, 0, 0 +29045,Clark County, MO, Mosourri, 6, -6:1, 7467, 1148, 1, 0, 0 +26047,Emmet County, MI, Michigan, 4, -5:1, 28677, 1387, 1, 0, 0 +17195,Whiteside County, IL, Illinois, 6, -6:1, 59623, 1148, 1, 0, 0 +06019,Fresno County, CA, California, 9, -8:1, 755730, 3240, 0, 1, 0 +48319,Mason County, TX, Texas, 7, -6:1, 3692, 1148, 1, 0, 0 +51117,Mecklenburg County, VA, Virginia, 2, -5:1, 31047, 1387, 1, 0, 0 +31093,Howard County, NE, Nebraska, 6, -7:1, 6458, 4647, 1, 0, 0 +36031,Essex County, NY, New York, 1, -5:1, 37548, 1387, 1, 0, 0 +26129,Ogemaw County, MI, Michigan, 4, -5:1, 21193, 1387, 1, 0, 0 +18055,Greene County, IN, Indiana, 4, -5:1, 33467, 1387, 1, 0, 0 +31151,Saline County, NE, Nebraska, 6, -7:1, 12966, 4647, 1, 0, 0 +20019,Chautauqua County, KS, Kansas, 6, -6:1, 4360, 1148, 1, 0, 0 +48507,Zavala County, TX, Texas, 7, -6:1, 11927, 1148, 1, 0, 0 +21127,Lawrence County, KY, Kentucky, 4, -5:1, 15647, 1387, 1, 0, 0 +48005,Angelina County, TX, Texas, 7, -6:1, 77351, 1148, 1, 0, 0 +29077,Greene County, MO, Mosourri, 6, -6:1, 226758, 1148, 1, 0, 0 +27003,Anoka County, MN, Minnesota, 5, -6:1, 292181, 1148, 1, 0, 0 +51017,Bath County, VA, Virginia, 2, -5:1, 4891, 1387, 1, 0, 0 +06071,San Bernardino County, CA, California, 9, -8:1, 1635234, 3240, 0, 1, 0 +13289,Twiggs County, GA, Georgia, 3, -5:1, 10126, 1387, 1, 0, 0 +33003,Carroll County, NH, New Hampshire, 0, -5:1, 39346, 1387, 1, 0, 0 +48143,Erath County, TX, Texas, 7, -6:1, 31562, 1148, 1, 0, 0 +45045,Greenville County, SC, South Carolina, 2, -5:1, 353845, 1387, 1, 0, 0 +20087,Jefferson County, KS, Kansas, 6, -6:1, 18243, 1148, 1, 0, 0 +49015,Emery County, UT, Utah, 8, -7:1, 10989, 4647, 1, 0, 0 +48479,Webb County, TX, Texas, 7, -6:1, 188166, 1148, 1, 0, 0 +39027,Clinton County, OH, Ohio, 4, -5:1, 39979, 1387, 1, 0, 0 +21033,Caldwell County, KY, Kentucky, 4, -6:1, 13314, 1148, 1, 0, 0 +06003,Alpine County, CA, California, 9, -8:1, 1209, 3240, 0, 1, 0 +47005,Benton County, TN, Tennesee, 3, -5:1, 16328, 1387, 1, 0, 0 +18183,Whitley County, IN, Indiana, 4, -5:1, 30459, 1387, 1, 0, 0 +22007,Assumption Parish, LA, Louisiana, 7, -6:1, 23015, 1148, 1, 0, 0 +48385,Real County, TX, Texas, 7, -6:1, 2687, 1148, 1, 0, 0 +12017,Citrus County, FL, Florida, 3, -5:1, 114068, 1387, 1, 0, 0 +29149,Oregon County, MO, Mosourri, 6, -6:1, 10164, 1148, 1, 0, 0 +08097,Pitkin County, CO, Colorado, 8, -7:1, 13423, 4647, 1, 0, 0 +18093,Lawrence County, IN, Indiana, 4, -5:1, 45615, 1387, 1, 0, 0 +36063,Niagara County, NY, New York, 1, -5:1, 218070, 1387, 1, 0, 0 +51053,Dinwiddie County, VA, Virginia, 2, -5:1, 24657, 1387, 1, 0, 0 +05101,Newton County, AR, Arkansas, 7, -6:1, 8180, 1148, 1, 0, 0 +12127,Volusia County, FL, Florida, 3, -5:1, 423409, 1387, 1, 0, 0 +37089,Henderson County, NC, North Carolina, 2, -5:1, 80822, 1387, 1, 0, 0 +54015,Clay County, WV, West Virginia, 2, -5:1, 10530, 1387, 1, 0, 0 +27007,Beltrami County, MN, Minnesota, 5, -6:1, 38729, 1148, 1, 0, 0 +29039,Cedar County, MO, Mosourri, 6, -6:1, 13215, 1148, 1, 0, 0 +32027,Pershing County, NV, Nevada, 8, -8:1, 5434, 3240, 0, 1, 0 +09011,New London County, CT, Connecticut, 0, -5:1, 245740, 1387, 1, 0, 0 +27095,Mille Lacs County, MN, Minnesota, 5, -6:1, 21044, 1148, 1, 0, 0 +46047,Fall River County, SD, South Dakota, 5, -6:1, 7133, 1148, 1, 0, 0 +41025,Harney County, OR, Oregon, 9, -8:1, 7198, 3240, 0, 1, 0 +49049,Utah County, UT, Utah, 8, -7:1, 335635, 4647, 1, 0, 0 +18161,Union County, IN, Indiana, 4, -5:1, 7263, 1387, 1, 0, 0 +02013,Aleutians East Borough, AK, Alaska, 9, -9:1, 2253, 1174, 0, 0, 1 +01085,Lowndes County, AL, Alabama, 3, -6:1, 12984, 1148, 1, 0, 0 +19029,Cass County, IA, Iowa, 5, -6:1, 14591, 1148, 1, 0, 0 +50019,Orleans County, VT, Vermont, 0, -5:1, 25296, 1387, 1, 0, 0 +37045,Cleveland County, NC, North Carolina, 2, -5:1, 92753, 1387, 1, 0, 0 +12081,Manatee County, FL, Florida, 3, -5:1, 239682, 1387, 1, 0, 0 +37155,Robeson County, NC, North Carolina, 2, -5:1, 115589, 1387, 1, 0, 0 +20111,Lyon County, KS, Kansas, 6, -6:1, 33920, 1148, 1, 0, 0 +38079,Rolette County, ND, North Dakota, 5, -6:1, 14219, 1148, 1, 0, 0 +06095,Solano County, CA, California, 9, -8:1, 377415, 3240, 0, 1, 0 +47035,Cumberland County, TN, Tennesee, 3, -5:1, 44291, 1387, 1, 0, 0 +05137,Stone County, AR, Arkansas, 7, -6:1, 11154, 1148, 1, 0, 0 +48241,Jasper County, TX, Texas, 7, -6:1, 33437, 1148, 1, 0, 0 +28029,Copiah County, MS, Missippi, 5, -6:1, 28944, 1148, 1, 0, 0 +20179,Sheridan County, KS, Kansas, 6, -6:1, 2741, 1148, 1, 0, 0 +53007,Chelan County, WA, Washiington, 9, -8:1, 60052, 3240, 0, 1, 0 +48177,Gonzales County, TX, Texas, 7, -6:1, 17551, 1148, 1, 0, 0 +53013,Columbia County, WA, Washiington, 9, -8:1, 4156, 3240, 0, 1, 0 +53073,Whatcom County, WA, Washiington, 9, -8:1, 156830, 3240, 0, 1, 0 +48481,Wharton County, TX, Texas, 7, -6:1, 40133, 1148, 1, 0, 0 +27105,Nobles County, MN, Minnesota, 5, -6:1, 19312, 1148, 1, 0, 0 +48161,Freestone County, TX, Texas, 7, -6:1, 17675, 1148, 1, 0, 0 +49013,Duchesne County, UT, Utah, 8, -7:1, 14481, 4647, 1, 0, 0 +55067,Langlade County, WI, Wisconnsin, 5, -6:1, 20466, 1148, 1, 0, 0 +45019,Charleston County, SC, South Carolina, 2, -5:1, 316482, 1387, 1, 0, 0 +47063,Hamblen County, TN, Tennesee, 3, -5:1, 54050, 1387, 1, 0, 0 +48281,Lampasas County, TX, Texas, 7, -6:1, 17775, 1148, 1, 0, 0 +20205,Wilson County, KS, Kansas, 6, -6:1, 10218, 1148, 1, 0, 0 +26161,Washtenaw County, MI, Michigan, 4, -5:1, 303069, 1387, 1, 0, 0 +54013,Calhoun County, WV, West Virginia, 2, -5:1, 7940, 1387, 1, 0, 0 +17007,Boone County, IL, Illinois, 6, -6:1, 38734, 1148, 1, 0, 0 +37051,Cumberland County, NC, North Carolina, 2, -5:1, 284629, 1387, 1, 0, 0 +28037,Franklin County, MS, Missippi, 5, -6:1, 8319, 1148, 1, 0, 0 +27023,Chippewa County, MN, Minnesota, 5, -6:1, 13053, 1148, 1, 0, 0 +08053,Hinsdale County, CO, Colorado, 8, -7:1, 737, 4647, 1, 0, 0 +46109,Roberts County, SD, South Dakota, 5, -7:1, 9786, 4647, 1, 0, 0 +08027,Custer County, CO, Colorado, 8, -7:1, 3449, 4647, 1, 0, 0 +40093,Major County, OK, Oklahoma, 7, -6:1, 7829, 1148, 1, 0, 0 +26109,Menominee County, MI, Michigan, 4, -5:1, 24468, 1387, 1, 0, 0 +34037,Sussex County, NJ, New Jersey, 0, -5:1, 143030, 1387, 1, 0, 0 +24027,Howard County, MD, Maryland, 2, -5:1, 236388, 1387, 1, 0, 0 +37137,Pamlico County, NC, North Carolina, 2, -5:1, 12345, 1387, 1, 0, 0 +47157,Shelby County, TN, Tennesee, 3, -6:1, 868825, 1148, 1, 0, 0 +38037,Grant County, ND, North Dakota, 5, -6:1, 2969, 1148, 1, 0, 0 +41033,Josephine County, OR, Oregon, 9, -8:1, 74377, 3240, 0, 1, 0 +42003,Allegheny County, PA, Pennsylvania, 1, -5:1, 1268446, 1387, 1, 0, 0 +51620,Franklin city, VA, Virginia, 2, -5:1, 8685, 1387, 1, 0, 0 +05085,Lonoke County, AR, Arkansas, 7, -6:1, 50156, 1148, 1, 0, 0 +40021,Cherokee County, OK, Oklahoma, 7, -6:1, 39138, 1148, 1, 0, 0 +45089,Williamsburg County, SC, South Carolina, 2, -5:1, 37121, 1387, 1, 0, 0 +51059,Fairfax County, VA, Virginia, 2, -5:1, 929239, 1387, 1, 0, 0 +08039,Elbert County, CO, Colorado, 8, -7:1, 18600, 4647, 1, 0, 0 +05051,Garland County, AR, Arkansas, 7, -6:1, 83976, 1148, 1, 0, 0 +21181,Nicholas County, KY, Kentucky, 4, -5:1, 6998, 1387, 1, 0, 0 +19037,Chickasaw County, IA, Iowa, 5, -6:1, 13441, 1148, 1, 0, 0 +48285,Lavaca County, TX, Texas, 7, -6:1, 18813, 1148, 1, 0, 0 +20177,Shawnee County, KS, Kansas, 6, -6:1, 165348, 1148, 1, 0, 0 +39123,Ottawa County, OH, Ohio, 4, -5:1, 40983, 1387, 1, 0, 0 +18151,Steuben County, IN, Indiana, 4, -5:1, 31450, 1387, 1, 0, 0 +17121,Marion County, IL, Illinois, 6, -6:1, 41883, 1148, 1, 0, 0 +17059,Gallatin County, IL, Illinois, 6, -6:1, 6642, 1148, 1, 0, 0 +56007,Carbon County, WY, Wyoming, 8, -7:1, 15575, 4647, 1, 0, 0 +51595,Emporia city, VA, Virginia, 2, -5:1, 5474, 1387, 1, 0, 0 +29085,Hickory County, MO, Mosourri, 6, -6:1, 8617, 1148, 1, 0, 0 +08017,Cheyenne County, CO, Colorado, 8, -7:1, 2346, 4647, 1, 0, 0 +46025,Clark County, SD, South Dakota, 5, -6:1, 4337, 1148, 1, 0, 0 +39001,Adams County, OH, Ohio, 4, -5:1, 28587, 1387, 1, 0, 0 +18033,De Kalb County, IN, Indiana, 4, -5:1, 39330, 1387, 1, 0, 0 +29041,Chariton County, MO, Mosourri, 6, -6:1, 8621, 1148, 1, 0, 0 +40089,McCurtain County, OK, Oklahoma, 7, -6:1, 34783, 1148, 1, 0, 0 +20189,Stevens County, KS, Kansas, 6, -6:1, 5371, 1148, 1, 0, 0 +48267,Kimble County, TX, Texas, 7, -6:1, 4124, 1148, 1, 0, 0 +05037,Cross County, AR, Arkansas, 7, -6:1, 19564, 1148, 1, 0, 0 +41013,Crook County, OR, Oregon, 9, -8:1, 17236, 3240, 0, 1, 0 +13163,Jefferson County, GA, Georgia, 3, -5:1, 17767, 1387, 1, 0, 0 +48313,Madison County, TX, Texas, 7, -6:1, 11889, 1148, 1, 0, 0 +36077,Otsego County, NY, New York, 1, -5:1, 60788, 1387, 1, 0, 0 +51007,Amelia County, VA, Virginia, 2, -5:1, 10367, 1387, 1, 0, 0 +40007,Beaver County, OK, Oklahoma, 7, -6:1, 6056, 1148, 1, 0, 0 +13085,Dawson County, GA, Georgia, 3, -5:1, 14851, 1387, 1, 0, 0 +48049,Brown County, TX, Texas, 7, -6:1, 37051, 1148, 1, 0, 0 +30021,Dawson County, MT, Montana, 6, -7:1, 8849, 4647, 1, 0, 0 +50015,Lamoille County, VT, Vermont, 0, -5:1, 21597, 1387, 1, 0, 0 +29099,Jefferson County, MO, Mosourri, 6, -6:1, 195675, 1148, 1, 0, 0 +48263,Kent County, TX, Texas, 7, -6:1, 880, 1148, 1, 0, 0 +41071,Yamhill County, OR, Oregon, 9, -8:1, 82085, 3240, 0, 1, 0 +42027,Centre County, PA, Pennsylvania, 1, -5:1, 132700, 1387, 1, 0, 0 +13147,Hart County, GA, Georgia, 3, -5:1, 21833, 1387, 1, 0, 0 +19003,Adams County, IA, Iowa, 5, -6:1, 4352, 1148, 1, 0, 0 +31077,Greeley County, NE, Nebraska, 6, -6:1, 2850, 1148, 1, 0, 0 +50003,Bennington County, VT, Vermont, 0, -5:1, 35968, 1387, 1, 0, 0 +19053,Decatur County, IA, Iowa, 5, -6:1, 8220, 1148, 1, 0, 0 +48133,Eastland County, TX, Texas, 7, -6:1, 17591, 1148, 1, 0, 0 +12071,Lee County, FL, Florida, 3, -5:1, 392895, 1387, 1, 0, 0 +13043,Candler County, GA, Georgia, 3, -5:1, 9078, 1387, 1, 0, 0 +05057,Hempstead County, AR, Arkansas, 7, -6:1, 22113, 1148, 1, 0, 0 +18025,Crawford County, IN, Indiana, 4, -5:1, 10582, 1387, 1, 0, 0 +26079,Kalkaska County, MI, Michigan, 4, -5:1, 15568, 1387, 1, 0, 0 +31183,Wheeler County, NE, Nebraska, 6, -7:1, 925, 4647, 1, 0, 0 +25001,Barnstable County, MA, Massachusetts, 0, -5:1, 208418, 1387, 1, 0, 0 +53023,Garfield County, WA, Washiington, 9, -8:1, 2330, 3240, 0, 1, 0 +38103,Wells County, ND, North Dakota, 5, -6:1, 5200, 1148, 1, 0, 0 +18133,Putnam County, IN, Indiana, 4, -5:1, 34468, 1387, 1, 0, 0 +21207,Russell County, KY, Kentucky, 4, -5:1, 16233, 1387, 1, 0, 0 +53067,Thurston County, WA, Washiington, 9, -8:1, 202255, 3240, 0, 1, 0 +06083,Santa Barbara County, CA, California, 9, -8:1, 389502, 3240, 0, 1, 0 +18027,Daviess County, IN, Indiana, 4, -5:1, 28987, 1387, 1, 0, 0 +48185,Grimes County, TX, Texas, 7, -6:1, 23293, 1148, 1, 0, 0 +17139,Moultrie County, IL, Illinois, 6, -6:1, 14410, 1148, 1, 0, 0 +20113,McPherson County, KS, Kansas, 6, -6:1, 28630, 1148, 1, 0, 0 +40031,Comanche County, OK, Oklahoma, 7, -6:1, 113508, 1148, 1, 0, 0 +18057,Hamilton County, IN, Indiana, 4, -5:1, 162597, 1387, 1, 0, 0 +29173,Ralls County, MO, Mosourri, 6, -6:1, 8813, 1148, 1, 0, 0 +17171,Scott County, IL, Illinois, 6, -6:1, 5610, 1148, 1, 0, 0 +48335,Mitchell County, TX, Texas, 7, -6:1, 9708, 1148, 1, 0, 0 +37187,Washington County, NC, North Carolina, 2, -5:1, 13615, 1387, 1, 0, 0 +37163,Sampson County, NC, North Carolina, 2, -5:1, 52438, 1387, 1, 0, 0 +48231,Hunt County, TX, Texas, 7, -6:1, 70893, 1148, 1, 0, 0 +56029,Park County, WY, Wyoming, 8, -7:1, 25782, 4647, 1, 0, 0 +51678,Lexington city, VA, Virginia, 2, -5:1, 7360, 1387, 1, 0, 0 +41043,Linn County, OR, Oregon, 9, -8:1, 104464, 3240, 0, 1, 0 +31123,Morrill County, NE, Nebraska, 6, -7:1, 5455, 4647, 1, 0, 0 +48211,Hemphill County, TX, Texas, 7, -6:1, 3529, 1148, 1, 0, 0 +22079,Rapides Parish, LA, Louisiana, 7, -6:1, 126763, 1148, 1, 0, 0 +28087,Lowndes County, MS, Missippi, 5, -6:1, 61208, 1148, 1, 0, 0 +37063,Durham County, NC, North Carolina, 2, -5:1, 202411, 1387, 1, 0, 0 +29019,Boone County, MO, Mosourri, 6, -6:1, 129098, 1148, 1, 0, 0 +48493,Wilson County, TX, Texas, 7, -6:1, 31423, 1148, 1, 0, 0 +20157,Republic County, KS, Kansas, 6, -6:1, 6102, 1148, 1, 0, 0 +41039,Lane County, OR, Oregon, 9, -8:1, 314068, 3240, 0, 1, 0 +54029,Hancock County, WV, West Virginia, 2, -5:1, 33973, 1387, 1, 0, 0 +48483,Wheeler County, TX, Texas, 7, -6:1, 5293, 1148, 1, 0, 0 +48113,Dallas County, TX, Texas, 7, -6:1, 2050865, 1148, 1, 0, 0 +28091,Marion County, MS, Missippi, 5, -6:1, 26386, 1148, 1, 0, 0 +29155,Pemiscot County, MO, Mosourri, 6, -6:1, 21516, 1148, 1, 0, 0 +16001,Ada County, ID, Idaho, 8, -7:1, 275687, 4647, 1, 0, 0 +16053,Jerome County, ID, Idaho, 8, -7:1, 17962, 4647, 1, 0, 0 +06065,Riverside County, CA, California, 9, -8:1, 1478838, 3240, 0, 1, 0 +18029,Dearborn County, IN, Indiana, 4, -5:1, 47206, 1387, 1, 0, 0 +42079,Luzerne County, PA, Pennsylvania, 1, -5:1, 313767, 1387, 1, 0, 0 +55003,Ashland County, WI, Wisconnsin, 5, -6:1, 16474, 1148, 1, 0, 0 +18105,Monroe County, IN, Indiana, 4, -5:1, 115130, 1387, 1, 0, 0 +21081,Grant County, KY, Kentucky, 4, -6:1, 20347, 1148, 1, 0, 0 +46065,Hughes County, SD, South Dakota, 5, -7:1, 15373, 4647, 1, 0, 0 +45027,Clarendon County, SC, South Carolina, 2, -5:1, 30814, 1387, 1, 0, 0 +20181,Sherman County, KS, Kansas, 6, -6:1, 6511, 1148, 1, 0, 0 +27145,Stearns County, MN, Minnesota, 5, -6:1, 128094, 1148, 1, 0, 0 +39069,Henry County, OH, Ohio, 4, -5:1, 29923, 1387, 1, 0, 0 +26003,Alger County, MI, Michigan, 4, -5:1, 9887, 1387, 1, 0, 0 +05133,Sevier County, AR, Arkansas, 7, -6:1, 14623, 1148, 1, 0, 0 +54101,Webster County, WV, West Virginia, 2, -5:1, 10230, 1387, 1, 0, 0 +51630,Fredericksburg city, VA, Virginia, 2, -5:1, 21686, 1387, 1, 0, 0 +17085,Jo Daviess County, IL, Illinois, 6, -6:1, 21468, 1148, 1, 0, 0 +48165,Gaines County, TX, Texas, 7, -6:1, 14992, 1148, 1, 0, 0 +20115,Marion County, KS, Kansas, 6, -6:1, 13593, 1148, 1, 0, 0 +41055,Sherman County, OR, Oregon, 9, -8:1, 1789, 3240, 0, 1, 0 +13115,Floyd County, GA, Georgia, 3, -5:1, 85185, 1387, 1, 0, 0 +21089,Greenup County, KY, Kentucky, 4, -6:1, 36874, 1148, 1, 0, 0 +06087,Santa Cruz County, CA, California, 9, -8:1, 242994, 3240, 0, 1, 0 +27139,Scott County, MN, Minnesota, 5, -6:1, 79031, 1148, 1, 0, 0 +47077,Henderson County, TN, Tennesee, 3, -5:1, 24424, 1387, 1, 0, 0 +53055,San Juan County, WA, Washiington, 9, -8:1, 12493, 3240, 0, 1, 0 +35006,Cibola County, NM, New Mexico, 8, -7:1, 26250, 4647, 1, 0, 0 +19193,Woodbury County, IA, Iowa, 5, -6:1, 101672, 1148, 1, 0, 0 +46073,Jerauld County, SD, South Dakota, 5, -7:1, 2222, 4647, 1, 0, 0 +27153,Todd County, MN, Minnesota, 5, -6:1, 24020, 1148, 1, 0, 0 +48065,Carson County, TX, Texas, 7, -6:1, 6696, 1148, 1, 0, 0 +47161,Stewart County, TN, Tennesee, 3, -6:1, 11545, 1148, 1, 0, 0 +42093,Montour County, PA, Pennsylvania, 1, -5:1, 17730, 1387, 1, 0, 0 +18053,Grant County, IN, Indiana, 4, -5:1, 72570, 1387, 1, 0, 0 +26019,Benzie County, MI, Michigan, 4, -5:1, 14678, 1387, 1, 0, 0 +29141,Morgan County, MO, Mosourri, 6, -6:1, 18434, 1148, 1, 0, 0 +13025,Brantley County, GA, Georgia, 3, -5:1, 13571, 1387, 1, 0, 0 +13301,Warren County, GA, Georgia, 3, -5:1, 6059, 1387, 1, 0, 0 +54077,Preston County, WV, West Virginia, 2, -5:1, 29811, 1387, 1, 0, 0 +46033,Custer County, SD, South Dakota, 5, -6:1, 6930, 1148, 1, 0, 0 +22005,Ascension Parish, LA, Louisiana, 7, -6:1, 71628, 1148, 1, 0, 0 +08055,Huerfano County, CO, Colorado, 8, -7:1, 6813, 4647, 1, 0, 0 +47107,McMinn County, TN, Tennesee, 3, -6:1, 46283, 1148, 1, 0, 0 +01027,Clay County, AL, Alabama, 3, -6:1, 13970, 1148, 1, 0, 0 +48303,Lubbock County, TX, Texas, 7, -6:1, 229475, 1148, 1, 0, 0 +08123,Weld County, CO, Colorado, 8, -7:1, 159429, 4647, 1, 0, 0 +28135,Tallahatchie County, MS, Missippi, 5, -6:1, 14893, 1148, 1, 0, 0 +08023,Costilla County, CO, Colorado, 8, -7:1, 3641, 4647, 1, 0, 0 +48279,Lamb County, TX, Texas, 7, -6:1, 14760, 1148, 1, 0, 0 +29101,Johnson County, MO, Mosourri, 6, -6:1, 47644, 1148, 1, 0, 0 +40045,Ellis County, OK, Oklahoma, 7, -6:1, 4291, 1148, 1, 0, 0 +55121,Trempealeau County, WI, Wisconnsin, 5, -6:1, 26469, 1148, 1, 0, 0 +22119,Webster Parish, LA, Louisiana, 7, -6:1, 42707, 1148, 1, 0, 0 +19021,Buena Vista County, IA, Iowa, 5, -6:1, 19454, 1148, 1, 0, 0 +47015,Cannon County, TN, Tennesee, 3, -5:1, 12139, 1387, 1, 0, 0 +21173,Montgomery County, KY, Kentucky, 4, -5:1, 20932, 1387, 1, 0, 0 +47169,Trousdale County, TN, Tennesee, 3, -6:1, 6844, 1148, 1, 0, 0 +31049,Deuel County, NE, Nebraska, 6, -6:1, 2029, 1148, 1, 0, 0 +19169,Story County, IA, Iowa, 5, -6:1, 75268, 1148, 1, 0, 0 +17157,Randolph County, IL, Illinois, 6, -6:1, 33489, 1148, 1, 0, 0 +17037,DeKalb County, IL, Illinois, 6, -6:1, 84169, 1148, 1, 0, 0 +40067,Jefferson County, OK, Oklahoma, 7, -6:1, 6583, 1148, 1, 0, 0 +47185,White County, TN, Tennesee, 3, -6:1, 22708, 1148, 1, 0, 0 +05089,Marion County, AR, Arkansas, 7, -6:1, 14918, 1148, 1, 0, 0 +20083,Hodgeman County, KS, Kansas, 6, -6:1, 2209, 1148, 1, 0, 0 +13135,Gwinnett County, GA, Georgia, 3, -5:1, 522095, 1387, 1, 0, 0 +05095,Monroe County, AR, Arkansas, 7, -6:1, 10200, 1148, 1, 0, 0 +45083,Spartanburg County, SC, South Carolina, 2, -5:1, 247458, 1387, 1, 0, 0 +30055,McCone County, MT, Montana, 6, -7:1, 1964, 4647, 1, 0, 0 +05079,Lincoln County, AR, Arkansas, 7, -6:1, 14274, 1148, 1, 0, 0 +30105,Valley County, MT, Montana, 6, -7:1, 8195, 4647, 1, 0, 0 +26077,Kalamazoo County, MI, Michigan, 4, -5:1, 229660, 1387, 1, 0, 0 +06073,San Diego County, CA, California, 9, -8:1, 2780592, 3240, 0, 1, 0 +20135,Ness County, KS, Kansas, 6, -6:1, 3607, 1148, 1, 0, 0 +31175,Valley County, NE, Nebraska, 6, -7:1, 4602, 4647, 1, 0, 0 +19065,Fayette County, IA, Iowa, 5, -6:1, 21761, 1148, 1, 0, 0 +47091,Johnson County, TN, Tennesee, 3, -6:1, 16755, 1148, 1, 0, 0 +05007,Benton County, AR, Arkansas, 7, -6:1, 134162, 1148, 1, 0, 0 +46031,Corson County, SD, South Dakota, 5, -6:1, 4190, 1148, 1, 0, 0 +45009,Bamberg County, SC, South Carolina, 2, -5:1, 16498, 1387, 1, 0, 0 +31137,Phelps County, NE, Nebraska, 6, -7:1, 9908, 4647, 1, 0, 0 +01011,Bullock County, AL, Alabama, 3, -6:1, 11311, 1148, 1, 0, 0 +06005,Amador County, CA, California, 9, -8:1, 33334, 3240, 0, 1, 0 +29159,Pettis County, MO, Mosourri, 6, -6:1, 37069, 1148, 1, 0, 0 +13271,Telfair County, GA, Georgia, 3, -5:1, 11558, 1387, 1, 0, 0 +19101,Jefferson County, IA, Iowa, 5, -6:1, 17113, 1148, 1, 0, 0 +17191,Wayne County, IL, Illinois, 6, -6:1, 16989, 1148, 1, 0, 0 +20079,Harvey County, KS, Kansas, 6, -6:1, 34361, 1148, 1, 0, 0 +22019,Calcasieu Parish, LA, Louisiana, 7, -6:1, 180330, 1148, 1, 0, 0 +46095,Mellette County, SD, South Dakota, 5, -7:1, 2029, 4647, 1, 0, 0 +21227,Warren County, KY, Kentucky, 4, -5:1, 87323, 1387, 1, 0, 0 +30099,Teton County, MT, Montana, 6, -7:1, 6333, 4647, 1, 0, 0 +27055,Houston County, MN, Minnesota, 5, -6:1, 19267, 1148, 1, 0, 0 +33017,Strafford County, NH, New Hampshire, 0, -5:1, 108650, 1387, 1, 0, 0 +47159,Smith County, TN, Tennesee, 3, -6:1, 16368, 1148, 1, 0, 0 +44007,Providence County, RI, Rhode Island, 0, -5:1, 574038, 1387, 1, 0, 0 +16015,Boise County, ID, Idaho, 8, -7:1, 5114, 4647, 1, 0, 0 +56019,Johnson County, WY, Wyoming, 8, -7:1, 6824, 4647, 1, 0, 0 +01067,Henry County, AL, Alabama, 3, -6:1, 15836, 1148, 1, 0, 0 +30101,Toole County, MT, Montana, 6, -7:1, 4727, 4647, 1, 0, 0 +51177,Spotsylvania County, VA, Virginia, 2, -5:1, 83692, 1387, 1, 0, 0 +21029,Bullitt County, KY, Kentucky, 4, -6:1, 59304, 1148, 1, 0, 0 +40111,Okmulgee County, OK, Oklahoma, 7, -6:1, 38860, 1148, 1, 0, 0 +12123,Taylor County, FL, Florida, 3, -5:1, 18849, 1387, 1, 0, 0 +51135,Nottoway County, VA, Virginia, 2, -5:1, 14999, 1387, 1, 0, 0 +29133,Mississippi County, MO, Mosourri, 6, -6:1, 13395, 1148, 1, 0, 0 +01129,Washington County, AL, Alabama, 3, -6:1, 17677, 1148, 1, 0, 0 +08035,Douglas County, CO, Colorado, 8, -7:1, 140975, 4647, 1, 0, 0 +55133,Waukesha County, WI, Wisconnsin, 5, -6:1, 353110, 1148, 1, 0, 0 +47131,Obion County, TN, Tennesee, 3, -6:1, 32219, 1148, 1, 0, 0 +42061,Huntingdon County, PA, Pennsylvania, 1, -5:1, 44599, 1387, 1, 0, 0 +46013,Brown County, SD, South Dakota, 5, -6:1, 35433, 1148, 1, 0, 0 +17187,Warren County, IL, Illinois, 6, -6:1, 18824, 1148, 1, 0, 0 +34041,Warren County, NJ, New Jersey, 0, -5:1, 98600, 1387, 1, 0, 0 +28043,Grenada County, MS, Missippi, 5, -6:1, 22427, 1148, 1, 0, 0 +36079,Putnam County, NY, New York, 1, -5:1, 93358, 1387, 1, 0, 0 +42067,Juniata County, PA, Pennsylvania, 1, -5:1, 22101, 1387, 1, 0, 0 +20109,Logan County, KS, Kansas, 6, -6:1, 2987, 1148, 1, 0, 0 +05115,Pope County, AR, Arkansas, 7, -6:1, 52059, 1148, 1, 0, 0 +13029,Bryan County, GA, Georgia, 3, -5:1, 23482, 1387, 1, 0, 0 +49039,Sanpete County, UT, Utah, 8, -7:1, 21452, 4647, 1, 0, 0 +29011,Barton County, MO, Mosourri, 6, -6:1, 12078, 1148, 1, 0, 0 +20097,Kiowa County, KS, Kansas, 6, -6:1, 3470, 1148, 1, 0, 0 +40081,Lincoln County, OK, Oklahoma, 7, -6:1, 31361, 1148, 1, 0, 0 +48297,Live Oak County, TX, Texas, 7, -6:1, 10137, 1148, 1, 0, 0 +48269,King County, TX, Texas, 7, -6:1, 363, 1148, 1, 0, 0 +37121,Mitchell County, NC, North Carolina, 2, -5:1, 14831, 1387, 1, 0, 0 +18043,Floyd County, IN, Indiana, 4, -5:1, 71990, 1387, 1, 0, 0 +31115,Loup County, NE, Nebraska, 6, -7:1, 666, 4647, 1, 0, 0 +48259,Kendall County, TX, Texas, 7, -6:1, 21222, 1148, 1, 0, 0 +18095,Madison County, IN, Indiana, 4, -5:1, 131360, 1387, 1, 0, 0 +27021,Cass County, MN, Minnesota, 5, -6:1, 26465, 1148, 1, 0, 0 +18123,Perry County, IN, Indiana, 4, -5:1, 19350, 1387, 1, 0, 0 +30033,Garfield County, MT, Montana, 6, -7:1, 1393, 4647, 1, 0, 0 +12131,Walton County, FL, Florida, 3, -5:1, 37410, 1387, 1, 0, 0 +16049,Idaho County, ID, Idaho, 8, -7:1, 15066, 4647, 1, 0, 0 +45013,Beaufort County, SC, South Carolina, 2, -5:1, 108959, 1387, 1, 0, 0 +20129,Morton County, KS, Kansas, 6, -6:1, 3440, 1148, 1, 0, 0 +08033,Dolores County, CO, Colorado, 8, -7:1, 1822, 4647, 1, 0, 0 +18169,Wabash County, IN, Indiana, 4, -5:1, 34537, 1387, 1, 0, 0 +39175,Wyandot County, OH, Ohio, 4, -5:1, 22826, 1387, 1, 0, 0 +12087,Monroe County, FL, Florida, 3, -5:1, 81203, 1387, 1, 0, 0 +28131,Stone County, MS, Missippi, 5, -6:1, 13166, 1148, 1, 0, 0 +18147,Spencer County, IN, Indiana, 4, -5:1, 20937, 1387, 1, 0, 0 +17075,Iroquois County, IL, Illinois, 6, -6:1, 31243, 1148, 1, 0, 0 +51043,Clarke County, VA, Virginia, 2, -5:1, 12779, 1387, 1, 0, 0 +46059,Hand County, SD, South Dakota, 5, -7:1, 4144, 4647, 1, 0, 0 +42035,Clinton County, PA, Pennsylvania, 1, -5:1, 37000, 1387, 1, 0, 0 +17185,Wabash County, IL, Illinois, 6, -6:1, 12630, 1148, 1, 0, 0 +39031,Coshocton County, OH, Ohio, 4, -5:1, 36115, 1387, 1, 0, 0 +01111,Randolph County, AL, Alabama, 3, -6:1, 19923, 1148, 1, 0, 0 +35028,Los Alamos County, NM, New Mexico, 8, -7:1, 18344, 4647, 1, 0, 0 +28089,Madison County, MS, Missippi, 5, -6:1, 72857, 1148, 1, 0, 0 +46117,Stanley County, SD, South Dakota, 5, -7:1, 2929, 4647, 1, 0, 0 +13185,Lowndes County, GA, Georgia, 3, -5:1, 85231, 1387, 1, 0, 0 +46023,Charles Mix County, SD, South Dakota, 5, -6:1, 9337, 1148, 1, 0, 0 +19161,Sac County, IA, Iowa, 5, -6:1, 11931, 1148, 1, 0, 0 +22011,Beauregard Parish, LA, Louisiana, 7, -6:1, 31976, 1148, 1, 0, 0 +19157,Poweshiek County, IA, Iowa, 5, -6:1, 18865, 1148, 1, 0, 0 +53041,Lewis County, WA, Washiington, 9, -8:1, 68163, 3240, 0, 1, 0 +05055,Greene County, AR, Arkansas, 7, -6:1, 36192, 1148, 1, 0, 0 +18153,Sullivan County, IN, Indiana, 4, -5:1, 19270, 1387, 1, 0, 0 +55061,Kewaunee County, WI, Wisconnsin, 5, -6:1, 19806, 1148, 1, 0, 0 +45015,Berkeley County, SC, South Carolina, 2, -5:1, 136544, 1387, 1, 0, 0 +41017,Deschutes County, OR, Oregon, 9, -8:1, 105640, 3240, 0, 1, 0 +21189,Owsley County, KY, Kentucky, 4, -5:1, 5404, 1387, 1, 0, 0 +01029,Cleburne County, AL, Alabama, 3, -6:1, 14308, 1148, 1, 0, 0 +42131,Wyoming County, PA, Pennsylvania, 1, -5:1, 29149, 1387, 1, 0, 0 +06077,San Joaquin County, CA, California, 9, -8:1, 550445, 3240, 0, 1, 0 +29005,Atchison County, MO, Mosourri, 6, -6:1, 6999, 1148, 1, 0, 0 +31009,Blaine County, NE, Nebraska, 6, -6:1, 578, 1148, 1, 0, 0 +37037,Chatham County, NC, North Carolina, 2, -5:1, 45406, 1387, 1, 0, 0 +22039,Evangeline Parish, LA, Louisiana, 7, -6:1, 34097, 1148, 1, 0, 0 +12099,Palm Beach County, FL, Florida, 3, -5:1, 1032625, 1387, 1, 0, 0 +01031,Coffee County, AL, Alabama, 3, -6:1, 42436, 1148, 1, 0, 0 +48123,DeWitt County, TX, Texas, 7, -6:1, 19661, 1148, 1, 0, 0 +54065,Morgan County, WV, West Virginia, 2, -5:1, 13640, 1387, 1, 0, 0 +47133,Overton County, TN, Tennesee, 3, -6:1, 19557, 1148, 1, 0, 0 +26133,Osceola County, MI, Michigan, 4, -5:1, 22106, 1387, 1, 0, 0 +40121,Pittsburg County, OK, Oklahoma, 7, -6:1, 42798, 1148, 1, 0, 0 +37047,Columbus County, NC, North Carolina, 2, -5:1, 52634, 1387, 1, 0, 0 +27047,Freeborn County, MN, Minnesota, 5, -6:1, 31584, 1148, 1, 0, 0 +28103,Noxubee County, MS, Missippi, 5, -6:1, 12366, 1148, 1, 0, 0 +48073,Cherokee County, TX, Texas, 7, -6:1, 42947, 1148, 1, 0, 0 +31149,Rock County, NE, Nebraska, 6, -7:1, 1743, 4647, 1, 0, 0 +19197,Wright County, IA, Iowa, 5, -6:1, 14003, 1148, 1, 0, 0 +47143,Rhea County, TN, Tennesee, 3, -6:1, 27836, 1148, 1, 0, 0 +24001,Allegany County, MD, Maryland, 2, -5:1, 71333, 1387, 1, 0, 0 +36085,Richmond County, NY, New York, 1, -5:1, 407123, 1387, 1, 0, 0 +21185,Oldham County, KY, Kentucky, 4, -5:1, 44395, 1387, 1, 0, 0 +28159,Winston County, MS, Missippi, 5, -6:1, 19387, 1148, 1, 0, 0 +47149,Rutherford County, TN, Tennesee, 3, -6:1, 166035, 1148, 1, 0, 0 +19017,Bremer County, IA, Iowa, 5, -6:1, 23411, 1148, 1, 0, 0 +41051,Multnomah County, OR, Oregon, 9, -8:1, 631082, 3240, 0, 1, 0 +42125,Washington County, PA, Pennsylvania, 1, -5:1, 205566, 1387, 1, 0, 0 +51013,Arlington County, VA, Virginia, 2, -5:1, 177275, 1387, 1, 0, 0 +22031,De Soto Parish, LA, Louisiana, 7, -6:1, 24921, 1148, 1, 0, 0 +19107,Keokuk County, IA, Iowa, 5, -6:1, 11499, 1148, 1, 0, 0 +13251,Screven County, GA, Georgia, 3, -5:1, 14431, 1387, 1, 0, 0 +38059,Morton County, ND, North Dakota, 5, -6:1, 24575, 1148, 1, 0, 0 +19011,Benton County, IA, Iowa, 5, -6:1, 25418, 1148, 1, 0, 0 +26023,Branch County, MI, Michigan, 4, -5:1, 43634, 1387, 1, 0, 0 +51720,Norton city, VA, Virginia, 2, -5:1, 4155, 1387, 1, 0, 0 +02130,Ketchikan Gateway Borough, AK, Alaska, 9, -9:1, 13443, 1174, 0, 0, 1 +35033,Mora County, NM, New Mexico, 8, -7:1, 4861, 4647, 1, 0, 0 +29071,Franklin County, MO, Mosourri, 6, -6:1, 91763, 1148, 1, 0, 0 +22043,Grant Parish, LA, Louisiana, 7, -6:1, 18990, 1148, 1, 0, 0 +54043,Lincoln County, WV, West Virginia, 2, -5:1, 22192, 1387, 1, 0, 0 +37113,Macon County, NC, North Carolina, 2, -5:1, 28338, 1387, 1, 0, 0 +17053,Ford County, IL, Illinois, 6, -6:1, 14084, 1148, 1, 0, 0 +37001,Alamance County, NC, North Carolina, 2, -5:1, 119397, 1387, 1, 0, 0 +38099,Walsh County, ND, North Dakota, 5, -6:1, 13532, 1148, 1, 0, 0 +42075,Lebanon County, PA, Pennsylvania, 1, -5:1, 117434, 1387, 1, 0, 0 +18097,Marion County, IN, Indiana, 4, -5:1, 813405, 1387, 1, 0, 0 +51023,Botetourt County, VA, Virginia, 2, -5:1, 28561, 1387, 1, 0, 0 +51171,Shenandoah County, VA, Virginia, 2, -5:1, 34663, 1387, 1, 0, 0 +18061,Harrison County, IN, Indiana, 4, -5:1, 34730, 1387, 1, 0, 0 +51035,Carroll County, VA, Virginia, 2, -5:1, 27873, 1387, 1, 0, 0 +51091,Highland County, VA, Virginia, 2, -5:1, 2499, 1387, 1, 0, 0 +20045,Douglas County, KS, Kansas, 6, -6:1, 93137, 1148, 1, 0, 0 +31059,Fillmore County, NE, Nebraska, 6, -6:1, 6929, 1148, 1, 0, 0 +36057,Montgomery County, NY, New York, 1, -5:1, 50755, 1387, 1, 0, 0 +20005,Atchison County, KS, Kansas, 6, -6:1, 16908, 1148, 1, 0, 0 +47147,Robertson County, TN, Tennesee, 3, -6:1, 53077, 1148, 1, 0, 0 +48043,Brewster County, TX, Texas, 7, -6:1, 8893, 1148, 1, 0, 0 +05117,Prairie County, AR, Arkansas, 7, -6:1, 9410, 1148, 1, 0, 0 +26157,Tuscola County, MI, Michigan, 4, -5:1, 58181, 1387, 1, 0, 0 +46061,Hanson County, SD, South Dakota, 5, -7:1, 2935, 4647, 1, 0, 0 +48061,Cameron County, TX, Texas, 7, -6:1, 326449, 1148, 1, 0, 0 +21077,Gallatin County, KY, Kentucky, 4, -6:1, 7182, 1148, 1, 0, 0 +42073,Lawrence County, PA, Pennsylvania, 1, -5:1, 94887, 1387, 1, 0, 0 +48137,Edwards County, TX, Texas, 7, -6:1, 3779, 1148, 1, 0, 0 +55011,Buffalo County, WI, Wisconnsin, 5, -6:1, 14298, 1148, 1, 0, 0 +42001,Adams County, PA, Pennsylvania, 1, -5:1, 86537, 1387, 1, 0, 0 +18037,Dubois County, IN, Indiana, 4, -5:1, 39682, 1387, 1, 0, 0 +19143,Osceola County, IA, Iowa, 5, -6:1, 6980, 1148, 1, 0, 0 +32005,Douglas County, NV, Nevada, 8, -8:1, 37051, 3240, 0, 1, 0 +50025,Windham County, VT, Vermont, 0, -5:1, 42650, 1387, 1, 0, 0 +45063,Lexington County, SC, South Carolina, 2, -5:1, 205260, 1387, 1, 0, 0 +12085,Martin County, FL, Florida, 3, -5:1, 115940, 1387, 1, 0, 0 +46093,Meade County, SD, South Dakota, 5, -7:1, 21911, 4647, 1, 0, 0 +27075,Lake County, MN, Minnesota, 5, -6:1, 10566, 1148, 1, 0, 0 +20033,Comanche County, KS, Kansas, 6, -6:1, 2012, 1148, 1, 0, 0 +21139,Livingston County, KY, Kentucky, 4, -5:1, 9432, 1387, 1, 0, 0 +19023,Butler County, IA, Iowa, 5, -6:1, 15693, 1148, 1, 0, 0 +48367,Parker County, TX, Texas, 7, -6:1, 81985, 1148, 1, 0, 0 +53019,Ferry County, WA, Washiington, 9, -8:1, 7170, 3240, 0, 1, 0 +09015,Windham County, CT, Connecticut, 0, -5:1, 105121, 1387, 1, 0, 0 +48315,Marion County, TX, Texas, 7, -6:1, 10886, 1148, 1, 0, 0 +21165,Menifee County, KY, Kentucky, 4, -5:1, 5736, 1387, 1, 0, 0 +53043,Lincoln County, WA, Washiington, 9, -8:1, 9734, 3240, 0, 1, 0 +19109,Kossuth County, IA, Iowa, 5, -6:1, 17738, 1148, 1, 0, 0 +40035,Craig County, OK, Oklahoma, 7, -6:1, 14450, 1148, 1, 0, 0 +51037,Charlotte County, VA, Virginia, 2, -5:1, 12259, 1387, 1, 0, 0 +48429,Stephens County, TX, Texas, 7, -6:1, 9811, 1148, 1, 0, 0 +30093,Silver Bow County, MT, Montana, 6, -7:1, 34560, 4647, 1, 0, 0 +54083,Randolph County, WV, West Virginia, 2, -5:1, 28658, 1387, 1, 0, 0 +55078,Menominee County, WI, Wisconnsin, 5, -6:1, 4779, 1148, 1, 0, 0 +21183,Ohio County, KY, Kentucky, 4, -5:1, 22005, 1387, 1, 0, 0 +27063,Jackson County, MN, Minnesota, 5, -6:1, 11529, 1148, 1, 0, 0 +08009,Baca County, CO, Colorado, 8, -7:1, 4365, 4647, 1, 0, 0 +06053,Monterey County, CA, California, 9, -8:1, 365605, 3240, 0, 1, 0 +29161,Phelps County, MO, Mosourri, 6, -6:1, 38592, 1148, 1, 0, 0 +29067,Douglas County, MO, Mosourri, 6, -6:1, 12422, 1148, 1, 0, 0 +39111,Monroe County, OH, Ohio, 4, -5:1, 15357, 1387, 1, 0, 0 +48189,Hale County, TX, Texas, 7, -6:1, 36676, 1148, 1, 0, 0 +13257,Stephens County, GA, Georgia, 3, -5:1, 25421, 1387, 1, 0, 0 +29167,Polk County, MO, Mosourri, 6, -6:1, 25530, 1148, 1, 0, 0 +36071,Orange County, NY, New York, 1, -5:1, 329220, 1387, 1, 0, 0 +19047,Crawford County, IA, Iowa, 5, -6:1, 16446, 1148, 1, 0, 0 +46085,Lyman County, SD, South Dakota, 5, -7:1, 3768, 4647, 1, 0, 0 +12091,Okaloosa County, FL, Florida, 3, -5:1, 169289, 1387, 1, 0, 0 +29153,Ozark County, MO, Mosourri, 6, -6:1, 9897, 1148, 1, 0, 0 +48109,Culberson County, TX, Texas, 7, -6:1, 3050, 1148, 1, 0, 0 +18009,Blackford County, IN, Indiana, 4, -5:1, 13910, 1387, 1, 0, 0 +40033,Cotton County, OK, Oklahoma, 7, -6:1, 6705, 1148, 1, 0, 0 +48155,Foard County, TX, Texas, 7, -6:1, 1699, 1148, 1, 0, 0 +28073,Lamar County, MS, Missippi, 5, -6:1, 36888, 1148, 1, 0, 0 +17107,Logan County, IL, Illinois, 6, -6:1, 31289, 1148, 1, 0, 0 +38013,Burke County, ND, North Dakota, 5, -6:1, 2266, 1148, 1, 0, 0 +29075,Gentry County, MO, Mosourri, 6, -6:1, 6938, 1148, 1, 0, 0 +35013,Dona Ana County, NM, New Mexico, 8, -7:1, 169165, 4647, 1, 0, 0 +51107,Loudoun County, VA, Virginia, 2, -5:1, 143940, 1387, 1, 0, 0 +06023,Humboldt County, CA, California, 9, -8:1, 122262, 3240, 0, 1, 0 +21193,Perry County, KY, Kentucky, 4, -5:1, 31049, 1387, 1, 0, 0 +20203,Wichita County, KS, Kansas, 6, -6:1, 2643, 1148, 1, 0, 0 +01053,Escambia County, AL, Alabama, 3, -6:1, 36740, 1148, 1, 0, 0 +22049,Jackson Parish, LA, Louisiana, 7, -6:1, 15566, 1148, 1, 0, 0 +42025,Carbon County, PA, Pennsylvania, 1, -5:1, 58857, 1387, 1, 0, 0 +32023,Nye County, NV, Nevada, 8, -8:1, 28799, 3240, 0, 1, 0 +32031,Washoe County, NV, Nevada, 8, -8:1, 313660, 3240, 0, 1, 0 +31127,Nemaha County, NE, Nebraska, 6, -7:1, 7697, 4647, 1, 0, 0 +48121,Denton County, TX, Texas, 7, -6:1, 384020, 1148, 1, 0, 0 +39163,Vinton County, OH, Ohio, 4, -5:1, 12158, 1387, 1, 0, 0 +27029,Clearwater County, MN, Minnesota, 5, -6:1, 8285, 1148, 1, 0, 0 +39081,Jefferson County, OH, Ohio, 4, -5:1, 74558, 1387, 1, 0, 0 +08099,Prowers County, CO, Colorado, 8, -7:1, 13729, 4647, 1, 0, 0 +49005,Cache County, UT, Utah, 8, -7:1, 86949, 4647, 1, 0, 0 +31001,Adams County, NE, Nebraska, 6, -6:1, 29464, 1148, 1, 0, 0 +29111,Lewis County, MO, Mosourri, 6, -6:1, 10199, 1148, 1, 0, 0 +45011,Barnwell County, SC, South Carolina, 2, -5:1, 21766, 1387, 1, 0, 0 +36035,Fulton County, NY, New York, 1, -5:1, 52914, 1387, 1, 0, 0 +05065,Izard County, AR, Arkansas, 7, -6:1, 13093, 1148, 1, 0, 0 +39019,Carroll County, OH, Ohio, 4, -5:1, 29095, 1387, 1, 0, 0 +38075,Renville County, ND, North Dakota, 5, -6:1, 2808, 1148, 1, 0, 0 +08109,Saguache County, CO, Colorado, 8, -7:1, 6076, 4647, 1, 0, 0 +46037,Day County, SD, South Dakota, 5, -6:1, 6400, 1148, 1, 0, 0 +08089,Otero County, CO, Colorado, 8, -7:1, 20671, 4647, 1, 0, 0 +21217,Taylor County, KY, Kentucky, 4, -5:1, 22943, 1387, 1, 0, 0 +31071,Garfield County, NE, Nebraska, 6, -6:1, 2039, 1148, 1, 0, 0 +09007,Middlesex County, CT, Connecticut, 0, -5:1, 150034, 1387, 1, 0, 0 +27111,Otter Tail County, MN, Minnesota, 5, -6:1, 54911, 1148, 1, 0, 0 +48375,Potter County, TX, Texas, 7, -6:1, 108943, 1148, 1, 0, 0 +47041,DeKalb County, TN, Tennesee, 3, -5:1, 15943, 1387, 1, 0, 0 +48011,Armstrong County, TX, Texas, 7, -6:1, 2164, 1148, 1, 0, 0 +48331,Milam County, TX, Texas, 7, -6:1, 24286, 1148, 1, 0, 0 +55009,Brown County, WI, Wisconnsin, 5, -6:1, 215373, 1148, 1, 0, 0 +21001,Adair County, KY, Kentucky, 4, -6:1, 16447, 1148, 1, 0, 0 +40077,Latimer County, OK, Oklahoma, 7, -6:1, 10321, 1148, 1, 0, 0 +48333,Mills County, TX, Texas, 7, -6:1, 4723, 1148, 1, 0, 0 +20141,Osborne County, KS, Kansas, 6, -6:1, 4712, 1148, 1, 0, 0 +37017,Bladen County, NC, North Carolina, 2, -5:1, 30717, 1387, 1, 0, 0 +19181,Warren County, IA, Iowa, 5, -6:1, 40196, 1148, 1, 0, 0 +12013,Calhoun County, FL, Florida, 3, -5:1, 12420, 1387, 1, 0, 0 +39105,Meigs County, OH, Ohio, 4, -5:1, 24006, 1387, 1, 0, 0 +37169,Stokes County, NC, North Carolina, 2, -5:1, 43292, 1387, 1, 0, 0 +55123,Vernon County, WI, Wisconnsin, 5, -6:1, 27343, 1148, 1, 0, 0 +41011,Coos County, OR, Oregon, 9, -8:1, 62162, 3240, 0, 1, 0 +31085,Hayes County, NE, Nebraska, 6, -6:1, 1069, 1148, 1, 0, 0 +13051,Chatham County, GA, Georgia, 3, -5:1, 225543, 1387, 1, 0, 0 +26143,Roscommon County, MI, Michigan, 4, -5:1, 23467, 1387, 1, 0, 0 +29093,Iron County, MO, Mosourri, 6, -6:1, 10871, 1148, 1, 0, 0 +48075,Childress County, TX, Texas, 7, -6:1, 7532, 1148, 1, 0, 0 +35031,McKinley County, NM, New Mexico, 8, -7:1, 67558, 4647, 1, 0, 0 +12039,Gadsden County, FL, Florida, 3, -5:1, 44043, 1387, 1, 0, 0 +47123,Monroe County, TN, Tennesee, 3, -6:1, 34830, 1148, 1, 0, 0 +17045,Edgar County, IL, Illinois, 6, -6:1, 19652, 1148, 1, 0, 0 +20187,Stanton County, KS, Kansas, 6, -6:1, 2265, 1148, 1, 0, 0 +19059,Dickinson County, IA, Iowa, 5, -6:1, 16236, 1148, 1, 0, 0 +29073,Gasconade County, MO, Mosourri, 6, -6:1, 14890, 1148, 1, 0, 0 +49035,Salt Lake County, UT, Utah, 8, -7:1, 850667, 4647, 1, 0, 0 +13215,Muscogee County, GA, Georgia, 3, -5:1, 182752, 1387, 1, 0, 0 +38057,Mercer County, ND, North Dakota, 5, -6:1, 9418, 1148, 1, 0, 0 +16081,Teton County, ID, Idaho, 8, -7:1, 5488, 4647, 1, 0, 0 +42085,Mercer County, PA, Pennsylvania, 1, -5:1, 121938, 1387, 1, 0, 0 +40085,Love County, OK, Oklahoma, 7, -6:1, 8536, 1148, 1, 0, 0 +29151,Osage County, MO, Mosourri, 6, -6:1, 12425, 1148, 1, 0, 0 +48213,Henderson County, TX, Texas, 7, -6:1, 68757, 1148, 1, 0, 0 +40059,Harper County, OK, Oklahoma, 7, -6:1, 3596, 1148, 1, 0, 0 +18031,Decatur County, IN, Indiana, 4, -5:1, 25562, 1387, 1, 0, 0 +45069,Marlboro County, SC, South Carolina, 2, -5:1, 29589, 1387, 1, 0, 0 +51113,Madison County, VA, Virginia, 2, -5:1, 12697, 1387, 1, 0, 0 +01093,Marion County, AL, Alabama, 3, -6:1, 30986, 1148, 1, 0, 0 +37135,Orange County, NC, North Carolina, 2, -5:1, 110116, 1387, 1, 0, 0 +19087,Henry County, IA, Iowa, 5, -6:1, 19983, 1148, 1, 0, 0 +37185,Warren County, NC, North Carolina, 2, -5:1, 18297, 1387, 1, 0, 0 +30051,Liberty County, MT, Montana, 6, -7:1, 2323, 4647, 1, 0, 0 +22037,East Feliciana Parish, LA, Louisiana, 7, -6:1, 20847, 1148, 1, 0, 0 +17159,Richland County, IL, Illinois, 6, -6:1, 16769, 1148, 1, 0, 0 +39087,Lawrence County, OH, Ohio, 4, -5:1, 64427, 1387, 1, 0, 0 +22117,Washington Parish, LA, Louisiana, 7, -6:1, 43059, 1148, 1, 0, 0 +48265,Kerr County, TX, Texas, 7, -6:1, 43248, 1148, 1, 0, 0 +12083,Marion County, FL, Florida, 3, -5:1, 241513, 1387, 1, 0, 0 +48055,Caldwell County, TX, Texas, 7, -6:1, 32447, 1148, 1, 0, 0 +13011,Banks County, GA, Georgia, 3, -5:1, 12798, 1387, 1, 0, 0 +51770,Roanoke city, VA, Virginia, 2, -5:1, 93749, 1387, 1, 0, 0 +20009,Barton County, KS, Kansas, 6, -6:1, 27641, 1148, 1, 0, 0 +21099,Hart County, KY, Kentucky, 4, -6:1, 16738, 1148, 1, 0, 0 +51063,Floyd County, VA, Virginia, 2, -5:1, 13091, 1387, 1, 0, 0 +40097,Mayes County, OK, Oklahoma, 7, -6:1, 37638, 1148, 1, 0, 0 +46075,Jones County, SD, South Dakota, 5, -7:1, 1219, 4647, 1, 0, 0 +38071,Ramsey County, ND, North Dakota, 5, -6:1, 12120, 1148, 1, 0, 0 +40055,Greer County, OK, Oklahoma, 7, -6:1, 6366, 1148, 1, 0, 0 +41047,Marion County, OR, Oregon, 9, -8:1, 268541, 3240, 0, 1, 0 +47089,Jefferson County, TN, Tennesee, 3, -6:1, 43663, 1148, 1, 0, 0 +38033,Golden Valley County, ND, North Dakota, 5, -6:1, 1876, 1148, 1, 0, 0 +40153,Woodward County, OK, Oklahoma, 7, -6:1, 18553, 1148, 1, 0, 0 +28027,Coahoma County, MS, Missippi, 5, -6:1, 31089, 1148, 1, 0, 0 +48487,Wilbarger County, TX, Texas, 7, -6:1, 13711, 1148, 1, 0, 0 +55021,Columbia County, WI, Wisconnsin, 5, -6:1, 51152, 1148, 1, 0, 0 +37161,Rutherford County, NC, North Carolina, 2, -5:1, 60842, 1387, 1, 0, 0 +18145,Shelby County, IN, Indiana, 4, -5:1, 43451, 1387, 1, 0, 0 +01013,Butler County, AL, Alabama, 3, -6:1, 21695, 1148, 1, 0, 0 +37175,Transylvania County, NC, North Carolina, 2, -5:1, 28481, 1387, 1, 0, 0 +20139,Osage County, KS, Kansas, 6, -6:1, 17139, 1148, 1, 0, 0 +21073,Franklin County, KY, Kentucky, 4, -6:1, 46438, 1148, 1, 0, 0 +06009,Calaveras County, CA, California, 9, -8:1, 39830, 3240, 0, 1, 0 +28097,Montgomery County, MS, Missippi, 5, -6:1, 12425, 1148, 1, 0, 0 +26081,Kent County, MI, Michigan, 4, -5:1, 545166, 1387, 1, 0, 0 +20031,Coffey County, KS, Kansas, 6, -6:1, 8696, 1148, 1, 0, 0 +39005,Ashland County, OH, Ohio, 4, -5:1, 52237, 1387, 1, 0, 0 +27077,Lake of the Woods County, MN, Minnesota, 5, -6:1, 4563, 1148, 1, 0, 0 +51099,King George County, VA, Virginia, 2, -5:1, 17236, 1387, 1, 0, 0 +39015,Brown County, OH, Ohio, 4, -5:1, 40795, 1387, 1, 0, 0 +54073,Pleasants County, WV, West Virginia, 2, -5:1, 7421, 1387, 1, 0, 0 +21023,Bracken County, KY, Kentucky, 4, -6:1, 8455, 1148, 1, 0, 0 +40069,Johnston County, OK, Oklahoma, 7, -6:1, 10346, 1148, 1, 0, 0 +39115,Morgan County, OH, Ohio, 4, -5:1, 14536, 1387, 1, 0, 0 +37181,Vance County, NC, North Carolina, 2, -5:1, 42155, 1387, 1, 0, 0 +13259,Stewart County, GA, Georgia, 3, -5:1, 5468, 1387, 1, 0, 0 +01099,Monroe County, AL, Alabama, 3, -6:1, 23965, 1148, 1, 0, 0 +13087,Decatur County, GA, Georgia, 3, -5:1, 27035, 1387, 1, 0, 0 +41009,Columbia County, OR, Oregon, 9, -8:1, 44416, 3240, 0, 1, 0 +49047,Uintah County, UT, Utah, 8, -7:1, 25660, 4647, 1, 0, 0 +51025,Brunswick County, VA, Virginia, 2, -5:1, 16716, 1387, 1, 0, 0 +28139,Tippah County, MS, Missippi, 5, -6:1, 21031, 1148, 1, 0, 0 +53061,Snohomish County, WA, Washiington, 9, -8:1, 587783, 3240, 0, 1, 0 +40125,Pottawatomie County, OK, Oklahoma, 7, -6:1, 62244, 1148, 1, 0, 0 +55023,Crawford County, WI, Wisconnsin, 5, -6:1, 16576, 1148, 1, 0, 0 +26069,Iosco County, MI, Michigan, 4, -5:1, 25111, 1387, 1, 0, 0 +19155,Pottawattamie County, IA, Iowa, 5, -6:1, 86174, 1148, 1, 0, 0 +04003,Cochise County, AZ, Arizona, 8, -7:1, 112564, 4647, 1, 0, 0 +36113,Warren County, NY, New York, 1, -5:1, 61261, 1387, 1, 0, 0 +46051,Grant County, SD, South Dakota, 5, -6:1, 8063, 1148, 1, 0, 0 +12069,Lake County, FL, Florida, 3, -5:1, 202207, 1387, 1, 0, 0 +16059,Lemhi County, ID, Idaho, 8, -7:1, 8030, 4647, 1, 0, 0 +45037,Edgefield County, SC, South Carolina, 2, -5:1, 20003, 1387, 1, 0, 0 +21143,Lyon County, KY, Kentucky, 4, -5:1, 8052, 1387, 1, 0, 0 +36009,Cattaraugus County, NY, New York, 1, -5:1, 85086, 1387, 1, 0, 0 +48019,Bandera County, TX, Texas, 7, -6:1, 15754, 1148, 1, 0, 0 +27087,Mahnomen County, MN, Minnesota, 5, -6:1, 5077, 1148, 1, 0, 0 +20041,Dickinson County, KS, Kansas, 6, -6:1, 19742, 1148, 1, 0, 0 +37097,Iredell County, NC, North Carolina, 2, -5:1, 113247, 1387, 1, 0, 0 +20195,Trego County, KS, Kansas, 6, -6:1, 3283, 1148, 1, 0, 0 +51790,Staunton city, VA, Virginia, 2, -5:1, 23346, 1387, 1, 0, 0 +37157,Rockingham County, NC, North Carolina, 2, -5:1, 90039, 1387, 1, 0, 0 +56021,Laramie County, WY, Wyoming, 8, -7:1, 78872, 4647, 1, 0, 0 +51600,Fairfax city, VA, Virginia, 2, -5:1, 20697, 1387, 1, 0, 0 +12003,Baker County, FL, Florida, 3, -5:1, 21103, 1387, 1, 0, 0 +46039,Deuel County, SD, South Dakota, 5, -6:1, 4512, 1148, 1, 0, 0 +51095,James City County, VA, Virginia, 2, -5:1, 44233, 1387, 1, 0, 0 +06063,Plumas County, CA, California, 9, -8:1, 20370, 3240, 0, 1, 0 +20069,Gray County, KS, Kansas, 6, -6:1, 5595, 1148, 1, 0, 0 +48191,Hall County, TX, Texas, 7, -6:1, 3644, 1148, 1, 0, 0 +51195,Wise County, VA, Virginia, 2, -5:1, 38599, 1387, 1, 0, 0 +40143,Tulsa County, OK, Oklahoma, 7, -6:1, 543539, 1148, 1, 0, 0 +22045,Iberia Parish, LA, Louisiana, 7, -6:1, 73154, 1148, 1, 0, 0 +53035,Kitsap County, WA, Washiington, 9, -8:1, 232623, 3240, 0, 1, 0 +45005,Allendale County, SC, South Carolina, 2, -5:1, 11460, 1387, 1, 0, 0 +42047,Elk County, PA, Pennsylvania, 1, -5:1, 34540, 1387, 1, 0, 0 +48275,Knox County, TX, Texas, 7, -6:1, 4252, 1148, 1, 0, 0 +51079,Greene County, VA, Virginia, 2, -5:1, 13991, 1387, 1, 0, 0 +18103,Miami County, IN, Indiana, 4, -5:1, 33543, 1387, 1, 0, 0 +29079,Grundy County, MO, Mosourri, 6, -6:1, 10159, 1148, 1, 0, 0 +37139,Pasquotank County, NC, North Carolina, 2, -5:1, 35474, 1387, 1, 0, 0 +42065,Jefferson County, PA, Pennsylvania, 1, -5:1, 46250, 1387, 1, 0, 0 +47025,Claiborne County, TN, Tennesee, 3, -5:1, 29529, 1387, 1, 0, 0 +17173,Shelby County, IL, Illinois, 6, -6:1, 22731, 1148, 1, 0, 0 +44009,Washington County, RI, Rhode Island, 0, -5:1, 120649, 1387, 1, 0, 0 +01015,Calhoun County, AL, Alabama, 3, -6:1, 117018, 1148, 1, 0, 0 +08087,Morgan County, CO, Colorado, 8, -7:1, 25087, 4647, 1, 0, 0 +55001,Adams County, WI, Wisconnsin, 5, -6:1, 18492, 1148, 1, 0, 0 +17129,Menard County, IL, Illinois, 6, -6:1, 12469, 1148, 1, 0, 0 +47093,Knox County, TN, Tennesee, 3, -6:1, 366846, 1148, 1, 0, 0 +21167,Mercer County, KY, Kentucky, 4, -5:1, 20704, 1387, 1, 0, 0 +31179,Wayne County, NE, Nebraska, 6, -7:1, 9400, 4647, 1, 0, 0 +38091,Steele County, ND, North Dakota, 5, -6:1, 2263, 1148, 1, 0, 0 +48491,Williamson County, TX, Texas, 7, -6:1, 223910, 1148, 1, 0, 0 +55025,Dane County, WI, Wisconnsin, 5, -6:1, 424586, 1148, 1, 0, 0 +31161,Sheridan County, NE, Nebraska, 6, -7:1, 6454, 4647, 1, 0, 0 +54109,Wyoming County, WV, West Virginia, 2, -5:1, 27380, 1387, 1, 0, 0 +40133,Seminole County, OK, Oklahoma, 7, -6:1, 24770, 1148, 1, 0, 0 +26105,Mason County, MI, Michigan, 4, -5:1, 27950, 1387, 1, 0, 0 +24510,Baltimore city, MD, Maryland, 2, -5:1, 645593, 1387, 1, 0, 0 +39025,Clermont County, OH, Ohio, 4, -5:1, 175960, 1387, 1, 0, 0 +42087,Mifflin County, PA, Pennsylvania, 1, -5:1, 46961, 1387, 1, 0, 0 +23009,Hancock County, ME, Maine, 0, -5:1, 49932, 1387, 1, 0, 0 +18039,Elkhart County, IN, Indiana, 4, -5:1, 172310, 1387, 1, 0, 0 +48157,Fort Bend County, TX, Texas, 7, -6:1, 337798, 1148, 1, 0, 0 +41015,Curry County, OR, Oregon, 9, -8:1, 21157, 3240, 0, 1, 0 +01061,Geneva County, AL, Alabama, 3, -6:1, 24944, 1148, 1, 0, 0 +53053,Pierce County, WA, Washiington, 9, -8:1, 676505, 3240, 0, 1, 0 +48299,Llano County, TX, Texas, 7, -6:1, 13480, 1148, 1, 0, 0 +55053,Jackson County, WI, Wisconnsin, 5, -6:1, 17735, 1148, 1, 0, 0 +38019,Cavalier County, ND, North Dakota, 5, -6:1, 5011, 1148, 1, 0, 0 +34031,Passaic County, NJ, New Jersey, 0, -5:1, 485737, 1387, 1, 0, 0 +26089,Leelanau County, MI, Michigan, 4, -5:1, 19142, 1387, 1, 0, 0 +32011,Eureka County, NV, Nevada, 8, -8:1, 1994, 3240, 0, 1, 0 +21007,Ballard County, KY, Kentucky, 4, -6:1, 8488, 1148, 1, 0, 0 +27015,Brown County, MN, Minnesota, 5, -6:1, 27037, 1148, 1, 0, 0 +48195,Hansford County, TX, Texas, 7, -6:1, 5347, 1148, 1, 0, 0 +54033,Harrison County, WV, West Virginia, 2, -5:1, 70891, 1387, 1, 0, 0 +47171,Unicoi County, TN, Tennesee, 3, -6:1, 17216, 1148, 1, 0, 0 +40103,Noble County, OK, Oklahoma, 7, -6:1, 11425, 1148, 1, 0, 0 +32009,Esmeralda County, NV, Nevada, 8, -8:1, 1135, 3240, 0, 1, 0 +08067,La Plata County, CO, Colorado, 8, -7:1, 40413, 4647, 1, 0, 0 +48167,Galveston County, TX, Texas, 7, -6:1, 245556, 1148, 1, 0, 0 +31031,Cherry County, NE, Nebraska, 6, -6:1, 6326, 1148, 1, 0, 0 +23027,Waldo County, ME, Maine, 0, -5:1, 36465, 1387, 1, 0, 0 +49057,Weber County, UT, Utah, 8, -7:1, 184065, 4647, 1, 0, 0 +06111,Ventura County, CA, California, 9, -8:1, 731967, 3240, 0, 1, 0 +46121,Todd County, SD, South Dakota, 5, -7:1, 9247, 4647, 1, 0, 0 +17019,Champaign County, IL, Illinois, 6, -6:1, 167788, 1148, 1, 0, 0 +05093,Mississippi County, AR, Arkansas, 7, -6:1, 50635, 1148, 1, 0, 0 +51173,Smyth County, VA, Virginia, 2, -5:1, 32757, 1387, 1, 0, 0 +26073,Isabella County, MI, Michigan, 4, -5:1, 58026, 1387, 1, 0, 0 +55043,Grant County, WI, Wisconnsin, 5, -6:1, 49340, 1148, 1, 0, 0 +26067,Ionia County, MI, Michigan, 4, -5:1, 61700, 1387, 1, 0, 0 +13281,Towns County, GA, Georgia, 3, -5:1, 8529, 1387, 1, 0, 0 +55057,Juneau County, WI, Wisconnsin, 5, -6:1, 23822, 1148, 1, 0, 0 +20075,Hamilton County, KS, Kansas, 6, -6:1, 2343, 1148, 1, 0, 0 +45087,Union County, SC, South Carolina, 2, -5:1, 30495, 1387, 1, 0, 0 +51085,Hanover County, VA, Virginia, 2, -5:1, 81975, 1387, 1, 0, 0 +26093,Livingston County, MI, Michigan, 4, -5:1, 146165, 1387, 1, 0, 0 +48079,Cochran County, TX, Texas, 7, -6:1, 3952, 1148, 1, 0, 0 +31075,Grant County, NE, Nebraska, 6, -6:1, 763, 1148, 1, 0, 0 +17147,Piatt County, IL, Illinois, 6, -6:1, 16400, 1148, 1, 0, 0 +55037,Florence County, WI, Wisconnsin, 5, -6:1, 5199, 1148, 1, 0, 0 +48097,Cooke County, TX, Texas, 7, -6:1, 32837, 1148, 1, 0, 0 +55077,Marquette County, WI, Wisconnsin, 5, -6:1, 15101, 1148, 1, 0, 0 +51193,Westmoreland County, VA, Virginia, 2, -5:1, 16282, 1387, 1, 0, 0 +09009,New Haven County, CT, Connecticut, 0, -5:1, 793504, 1387, 1, 0, 0 +08025,Crowley County, CO, Colorado, 8, -7:1, 4310, 4647, 1, 0, 0 +17025,Clay County, IL, Illinois, 6, -6:1, 14485, 1148, 1, 0, 0 +26107,Mecosta County, MI, Michigan, 4, -5:1, 40006, 1387, 1, 0, 0 +37065,Edgecombe County, NC, North Carolina, 2, -5:1, 55199, 1387, 1, 0, 0 +21013,Bell County, KY, Kentucky, 4, -6:1, 29133, 1148, 1, 0, 0 +27081,Lincoln County, MN, Minnesota, 5, -6:1, 6459, 1148, 1, 0, 0 +29107,Lafayette County, MO, Mosourri, 6, -6:1, 32653, 1148, 1, 0, 0 +49017,Garfield County, UT, Utah, 8, -7:1, 4272, 4647, 1, 0, 0 +18011,Boone County, IN, Indiana, 4, -5:1, 43843, 1387, 1, 0, 0 +55027,Dodge County, WI, Wisconnsin, 5, -6:1, 83261, 1148, 1, 0, 0 +51137,Orange County, VA, Virginia, 2, -5:1, 25408, 1387, 1, 0, 0 +42099,Perry County, PA, Pennsylvania, 1, -5:1, 44384, 1387, 1, 0, 0 +21021,Boyle County, KY, Kentucky, 4, -6:1, 27186, 1148, 1, 0, 0 +04011,Greenlee County, AZ, Arizona, 8, -7:1, 9304, 4647, 1, 0, 0 +31029,Chase County, NE, Nebraska, 6, -6:1, 4248, 1148, 1, 0, 0 +48305,Lynn County, TX, Texas, 7, -6:1, 6706, 1148, 1, 0, 0 +40095,Marshall County, OK, Oklahoma, 7, -6:1, 12326, 1148, 1, 0, 0 +48245,Jefferson County, TX, Texas, 7, -6:1, 241901, 1148, 1, 0, 0 +51031,Campbell County, VA, Virginia, 2, -5:1, 50335, 1387, 1, 0, 0 +51145,Powhatan County, VA, Virginia, 2, -5:1, 21950, 1387, 1, 0, 0 +48145,Falls County, TX, Texas, 7, -6:1, 17434, 1148, 1, 0, 0 +01009,Blount County, AL, Alabama, 3, -6:1, 46266, 1148, 1, 0, 0 +51157,Rappahannock County, VA, Virginia, 2, -5:1, 7269, 1387, 1, 0, 0 +26155,Shiawassee County, MI, Michigan, 4, -5:1, 72569, 1387, 1, 0, 0 +18085,Kosciusko County, IN, Indiana, 4, -5:1, 71207, 1387, 1, 0, 0 +20163,Rooks County, KS, Kansas, 6, -6:1, 5660, 1148, 1, 0, 0 +30035,Glacier County, MT, Montana, 6, -7:1, 12540, 4647, 1, 0, 0 +48365,Panola County, TX, Texas, 7, -6:1, 23070, 1148, 1, 0, 0 +51073,Gloucester County, VA, Virginia, 2, -5:1, 35081, 1387, 1, 0, 0 +13097,Douglas County, GA, Georgia, 3, -5:1, 89843, 1387, 1, 0, 0 +12031,Duval County, FL, Florida, 3, -5:1, 735733, 1387, 1, 0, 0 +39089,Licking County, OH, Ohio, 4, -5:1, 136896, 1387, 1, 0, 0 +36117,Wayne County, NY, New York, 1, -5:1, 94977, 1387, 1, 0, 0 +29083,Henry County, MO, Mosourri, 6, -6:1, 21232, 1148, 1, 0, 0 +33011,Hillsborough County, NH, New Hampshire, 0, -5:1, 363031, 1387, 1, 0, 0 +48025,Bee County, TX, Texas, 7, -6:1, 27718, 1148, 1, 0, 0 +47109,McNairy County, TN, Tennesee, 3, -6:1, 24048, 1148, 1, 0, 0 +51185,Tazewell County, VA, Virginia, 2, -5:1, 46766, 1387, 1, 0, 0 +45085,Sumter County, SC, South Carolina, 2, -5:1, 107127, 1387, 1, 0, 0 +42089,Monroe County, PA, Pennsylvania, 1, -5:1, 125583, 1387, 1, 0, 0 +05121,Randolph County, AR, Arkansas, 7, -6:1, 17802, 1148, 1, 0, 0 +48253,Jones County, TX, Texas, 7, -6:1, 18669, 1148, 1, 0, 0 +22035,East Carroll Parish, LA, Louisiana, 7, -6:1, 8905, 1148, 1, 0, 0 +48163,Frio County, TX, Texas, 7, -6:1, 15757, 1148, 1, 0, 0 +17021,Christian County, IL, Illinois, 6, -6:1, 34543, 1148, 1, 0, 0 +04012,La Paz County, AZ, Arizona, 8, -7:1, 14880, 4647, 1, 0, 0 +54041,Lewis County, WV, West Virginia, 2, -5:1, 17427, 1387, 1, 0, 0 +28035,Forrest County, MS, Missippi, 5, -6:1, 74364, 1148, 1, 0, 0 +21215,Spencer County, KY, Kentucky, 4, -5:1, 9660, 1387, 1, 0, 0 +18115,Ohio County, IN, Indiana, 4, -5:1, 5423, 1387, 1, 0, 0 +13307,Webster County, GA, Georgia, 3, -5:1, 2193, 1387, 1, 0, 0 +18003,Allen County, IN, Indiana, 4, -5:1, 314218, 1387, 1, 0, 0 +56009,Converse County, WY, Wyoming, 8, -7:1, 12337, 4647, 1, 0, 0 +20105,Lincoln County, KS, Kansas, 6, -6:1, 3338, 1148, 1, 0, 0 +21235,Whitley County, KY, Kentucky, 4, -5:1, 35938, 1387, 1, 0, 0 +39169,Wayne County, OH, Ohio, 4, -5:1, 110125, 1387, 1, 0, 0 +42107,Schuylkill County, PA, Pennsylvania, 1, -5:1, 148266, 1387, 1, 0, 0 +13277,Tift County, GA, Georgia, 3, -5:1, 36673, 1387, 1, 0, 0 +45033,Dillon County, SC, South Carolina, 2, -5:1, 29747, 1387, 1, 0, 0 +22033,East Baton Rouge Parish, LA, Louisiana, 7, -6:1, 394714, 1148, 1, 0, 0 +17151,Pope County, IL, Illinois, 6, -6:1, 4808, 1148, 1, 0, 0 +42095,Northampton County, PA, Pennsylvania, 1, -5:1, 258679, 1387, 1, 0, 0 +48183,Gregg County, TX, Texas, 7, -6:1, 113330, 1148, 1, 0, 0 +18113,Noble County, IN, Indiana, 4, -5:1, 42626, 1387, 1, 0, 0 +29059,Dallas County, MO, Mosourri, 6, -6:1, 15245, 1148, 1, 0, 0 +51057,Essex County, VA, Virginia, 2, -5:1, 9127, 1387, 1, 0, 0 +19167,Sioux County, IA, Iowa, 5, -6:1, 31280, 1148, 1, 0, 0 +21195,Pike County, KY, Kentucky, 4, -5:1, 72121, 1387, 1, 0, 0 +31081,Hamilton County, NE, Nebraska, 6, -6:1, 9471, 1148, 1, 0, 0 +12079,Madison County, FL, Florida, 3, -5:1, 17652, 1387, 1, 0, 0 +17005,Bond County, IL, Illinois, 6, -6:1, 15858, 1148, 1, 0, 0 +48197,Hardeman County, TX, Texas, 7, -6:1, 4591, 1148, 1, 0, 0 +20119,Meade County, KS, Kansas, 6, -6:1, 4424, 1148, 1, 0, 0 +17039,De Witt County, IL, Illinois, 6, -6:1, 16796, 1148, 1, 0, 0 +12023,Columbia County, FL, Florida, 3, -5:1, 52956, 1387, 1, 0, 0 +29015,Benton County, MO, Mosourri, 6, -6:1, 17040, 1148, 1, 0, 0 +17009,Brown County, IL, Illinois, 6, -6:1, 6822, 1148, 1, 0, 0 +19141,O-Brien County, IA, Iowa, 5, -6:1, 14910, 1148, 1, 0, 0 +48309,McLennan County, TX, Texas, 7, -6:1, 203446, 1148, 1, 0, 0 +13235,Pulaski County, GA, Georgia, 3, -5:1, 8401, 1387, 1, 0, 0 +12065,Jefferson County, FL, Florida, 3, -5:1, 12952, 1387, 1, 0, 0 +37123,Montgomery County, NC, North Carolina, 2, -5:1, 24080, 1387, 1, 0, 0 +28023,Clarke County, MS, Missippi, 5, -6:1, 18231, 1148, 1, 0, 0 +27019,Carver County, MN, Minnesota, 5, -6:1, 64674, 1148, 1, 0, 0 +30057,Madison County, MT, Montana, 6, -7:1, 6875, 4647, 1, 0, 0 +20093,Kearny County, KS, Kansas, 6, -6:1, 4177, 1148, 1, 0, 0 +39051,Fulton County, OH, Ohio, 4, -5:1, 41895, 1387, 1, 0, 0 +29215,Texas County, MO, Mosourri, 6, -6:1, 22357, 1148, 1, 0, 0 +19055,Delaware County, IA, Iowa, 5, -6:1, 18578, 1148, 1, 0, 0 +40061,Haskell County, OK, Oklahoma, 7, -6:1, 11368, 1148, 1, 0, 0 +31103,Keya Paha County, NE, Nebraska, 6, -7:1, 972, 4647, 1, 0, 0 +39003,Allen County, OH, Ohio, 4, -5:1, 107139, 1387, 1, 0, 0 +48317,Martin County, TX, Texas, 7, -6:1, 5043, 1148, 1, 0, 0 +40071,Kay County, OK, Oklahoma, 7, -6:1, 46698, 1148, 1, 0, 0 +37095,Hyde County, NC, North Carolina, 2, -5:1, 5612, 1387, 1, 0, 0 +20127,Morris County, KS, Kansas, 6, -6:1, 6169, 1148, 1, 0, 0 +30069,Petroleum County, MT, Montana, 6, -7:1, 499, 4647, 1, 0, 0 +06045,Mendocino County, CA, California, 9, -8:1, 83734, 3240, 0, 1, 0 +28011,Bolivar County, MS, Missippi, 5, -6:1, 40318, 1148, 1, 0, 0 +51101,King William County, VA, Virginia, 2, -5:1, 12768, 1387, 1, 0, 0 +02110,Juneau Borough, AK, Alaska, 9, -9:1, 30191, 1174, 0, 0, 1 +35043,Sandoval County, NM, New Mexico, 8, -7:1, 88049, 4647, 1, 0, 0 +13225,Peach County, GA, Georgia, 3, -5:1, 24462, 1387, 1, 0, 0 +39117,Morrow County, OH, Ohio, 4, -5:1, 31467, 1387, 1, 0, 0 +47009,Blount County, TN, Tennesee, 3, -5:1, 101295, 1387, 1, 0, 0 +28017,Chickasaw County, MS, Missippi, 5, -6:1, 18013, 1148, 1, 0, 0 +13267,Tattnall County, GA, Georgia, 3, -5:1, 18975, 1387, 1, 0, 0 +26101,Manistee County, MI, Michigan, 4, -5:1, 23330, 1387, 1, 0, 0 +13081,Crisp County, GA, Georgia, 3, -5:1, 20725, 1387, 1, 0, 0 +13317,Wilkes County, GA, Georgia, 3, -5:1, 10568, 1387, 1, 0, 0 +12001,Alachua County, FL, Florida, 3, -5:1, 198662, 1387, 1, 0, 0 +54051,Marshall County, WV, West Virginia, 2, -5:1, 35441, 1387, 1, 0, 0 +17197,Will County, IL, Illinois, 6, -6:1, 459189, 1148, 1, 0, 0 +54009,Brooke County, WV, West Virginia, 2, -5:1, 26004, 1387, 1, 0, 0 +42129,Westmoreland County, PA, Pennsylvania, 1, -5:1, 372103, 1387, 1, 0, 0 +33013,Merrimack County, NH, New Hampshire, 0, -5:1, 127381, 1387, 1, 0, 0 +27045,Fillmore County, MN, Minnesota, 5, -6:1, 20793, 1148, 1, 0, 0 +48091,Comal County, TX, Texas, 7, -6:1, 73391, 1148, 1, 0, 0 +28031,Covington County, MS, Missippi, 5, -6:1, 17802, 1148, 1, 0, 0 +06091,Sierra County, CA, California, 9, -8:1, 3380, 3240, 0, 1, 0 +48151,Fisher County, TX, Texas, 7, -6:1, 4241, 1148, 1, 0, 0 +48477,Washington County, TX, Texas, 7, -6:1, 29127, 1148, 1, 0, 0 +13091,Dodge County, GA, Georgia, 3, -5:1, 18108, 1387, 1, 0, 0 +48469,Victoria County, TX, Texas, 7, -6:1, 82650, 1148, 1, 0, 0 +40113,Osage County, OK, Oklahoma, 7, -6:1, 42838, 1148, 1, 0, 0 +47059,Greene County, TN, Tennesee, 3, -5:1, 60502, 1387, 1, 0, 0 +29217,Vernon County, MO, Mosourri, 6, -6:1, 19436, 1148, 1, 0, 0 +55111,Sauk County, WI, Wisconnsin, 5, -6:1, 53369, 1148, 1, 0, 0 +31061,Franklin County, NE, Nebraska, 6, -6:1, 3730, 1148, 1, 0, 0 +48495,Winkler County, TX, Texas, 7, -6:1, 7964, 1148, 1, 0, 0 +30007,Broadwater County, MT, Montana, 6, -7:1, 4132, 4647, 1, 0, 0 +20065,Graham County, KS, Kansas, 6, -6:1, 3204, 1148, 1, 0, 0 +45021,Cherokee County, SC, South Carolina, 2, -5:1, 49170, 1387, 1, 0, 0 +39061,Hamilton County, OH, Ohio, 4, -5:1, 847403, 1387, 1, 0, 0 +56003,Big Horn County, WY, Wyoming, 8, -7:1, 11380, 4647, 1, 0, 0 +13187,Lumpkin County, GA, Georgia, 3, -5:1, 18981, 1387, 1, 0, 0 +19051,Davis County, IA, Iowa, 5, -6:1, 8403, 1148, 1, 0, 0 +30017,Custer County, MT, Montana, 6, -7:1, 12035, 4647, 1, 0, 0 +54093,Tucker County, WV, West Virginia, 2, -5:1, 7631, 1387, 1, 0, 0 +20131,Nemaha County, KS, Kansas, 6, -6:1, 10132, 1148, 1, 0, 0 +37015,Bertie County, NC, North Carolina, 2, -5:1, 20459, 1387, 1, 0, 0 +55119,Taylor County, WI, Wisconnsin, 5, -6:1, 19313, 1148, 1, 0, 0 +13137,Habersham County, GA, Georgia, 3, -5:1, 31858, 1387, 1, 0, 0 +48031,Blanco County, TX, Texas, 7, -6:1, 8400, 1148, 1, 0, 0 +16057,Latah County, ID, Idaho, 8, -7:1, 32051, 4647, 1, 0, 0 +26029,Charlevoix County, MI, Michigan, 4, -5:1, 24436, 1387, 1, 0, 0 +37103,Jones County, NC, North Carolina, 2, -5:1, 9456, 1387, 1, 0, 0 +05015,Carroll County, AR, Arkansas, 7, -6:1, 22534, 1148, 1, 0, 0 +48111,Dallam County, TX, Texas, 7, -6:1, 6602, 1148, 1, 0, 0 +40141,Tillman County, OK, Oklahoma, 7, -6:1, 9503, 1148, 1, 0, 0 +26111,Midland County, MI, Michigan, 4, -5:1, 81842, 1387, 1, 0, 0 +18159,Tipton County, IN, Indiana, 4, -5:1, 16724, 1387, 1, 0, 0 +48359,Oldham County, TX, Texas, 7, -6:1, 2153, 1148, 1, 0, 0 +53051,Pend Oreille County, WA, Washiington, 9, -8:1, 11526, 3240, 0, 1, 0 +45017,Calhoun County, SC, South Carolina, 2, -5:1, 14051, 1387, 1, 0, 0 +42119,Union County, PA, Pennsylvania, 1, -5:1, 40897, 1387, 1, 0, 0 +48485,Wichita County, TX, Texas, 7, -6:1, 128904, 1148, 1, 0, 0 +30025,Fallon County, MT, Montana, 6, -7:1, 2941, 4647, 1, 0, 0 +01051,Elmore County, AL, Alabama, 3, -6:1, 61993, 1148, 1, 0, 0 +38093,Stutsman County, ND, North Dakota, 5, -6:1, 20964, 1148, 1, 0, 0 +05105,Perry County, AR, Arkansas, 7, -6:1, 9640, 1148, 1, 0, 0 +48081,Coke County, TX, Texas, 7, -6:1, 3367, 1148, 1, 0, 0 +19171,Tama County, IA, Iowa, 5, -6:1, 17739, 1148, 1, 0, 0 +29213,Taney County, MO, Mosourri, 6, -6:1, 34504, 1148, 1, 0, 0 +31185,York County, NE, Nebraska, 6, -6:1, 14512, 1148, 1, 0, 0 +39155,Trumbull County, OH, Ohio, 4, -5:1, 225066, 1387, 1, 0, 0 +46107,Potter County, SD, South Dakota, 5, -7:1, 2857, 4647, 1, 0, 0 +38023,Divide County, ND, North Dakota, 5, -6:1, 2366, 1148, 1, 0, 0 +13067,Cobb County, GA, Georgia, 3, -5:1, 566203, 1387, 1, 0, 0 +16035,Clearwater County, ID, Idaho, 8, -7:1, 9310, 4647, 1, 0, 0 +54061,Monongalia County, WV, West Virginia, 2, -5:1, 77505, 1387, 1, 0, 0 +50011,Franklin County, VT, Vermont, 0, -5:1, 44017, 1387, 1, 0, 0 +32001,Churchill County, NV, Nevada, 8, -8:1, 23293, 3240, 0, 1, 0 +47079,Henry County, TN, Tennesee, 3, -5:1, 30066, 1387, 1, 0, 0 +26017,Bay County, MI, Michigan, 4, -5:1, 110048, 1387, 1, 0, 0 +13017,Ben Hill County, GA, Georgia, 3, -5:1, 17496, 1387, 1, 0, 0 +39059,Guernsey County, OH, Ohio, 4, -5:1, 40994, 1387, 1, 0, 0 +05029,Conway County, AR, Arkansas, 7, -6:1, 19920, 1148, 1, 0, 0 +18023,Clinton County, IN, Indiana, 4, -5:1, 33215, 1387, 1, 0, 0 +46111,Sanborn County, SD, South Dakota, 5, -7:1, 2740, 4647, 1, 0, 0 +51121,Montgomery County, VA, Virginia, 2, -5:1, 75878, 1387, 1, 0, 0 +47001,Anderson County, TN, Tennesee, 3, -5:1, 71116, 1387, 1, 0, 0 +21097,Harrison County, KY, Kentucky, 4, -6:1, 17565, 1148, 1, 0, 0 +27083,Lyon County, MN, Minnesota, 5, -6:1, 24339, 1148, 1, 0, 0 +22069,Natchitoches Parish, LA, Louisiana, 7, -6:1, 37018, 1148, 1, 0, 0 +13305,Wayne County, GA, Georgia, 3, -5:1, 25437, 1387, 1, 0, 0 +21067,Fayette County, KY, Kentucky, 4, -6:1, 241749, 1148, 1, 0, 0 +27157,Wabasha County, MN, Minnesota, 5, -6:1, 20943, 1148, 1, 0, 0 +41001,Baker County, OR, Oregon, 9, -8:1, 16448, 3240, 0, 1, 0 +13283,Treutlen County, GA, Georgia, 3, -5:1, 6003, 1387, 1, 0, 0 +47051,Franklin County, TN, Tennesee, 3, -5:1, 37465, 1387, 1, 0, 0 +48283,La Salle County, TX, Texas, 7, -6:1, 6034, 1148, 1, 0, 0 +17011,Bureau County, IL, Illinois, 6, -6:1, 35530, 1148, 1, 0, 0 +26165,Wexford County, MI, Michigan, 4, -5:1, 29185, 1387, 1, 0, 0 +40091,McIntosh County, OK, Oklahoma, 7, -6:1, 19050, 1148, 1, 0, 0 +48209,Hays County, TX, Texas, 7, -6:1, 88536, 1148, 1, 0, 0 +38007,Billings County, ND, North Dakota, 5, -6:1, 1058, 1148, 1, 0, 0 +05017,Chicot County, AR, Arkansas, 7, -6:1, 14841, 1148, 1, 0, 0 +38041,Hettinger County, ND, North Dakota, 5, -6:1, 2924, 1148, 1, 0, 0 +13133,Greene County, GA, Georgia, 3, -5:1, 13651, 1387, 1, 0, 0 +20193,Thomas County, KS, Kansas, 6, -6:1, 8037, 1148, 1, 0, 0 +37149,Polk County, NC, North Carolina, 2, -5:1, 16835, 1387, 1, 0, 0 +55081,Monroe County, WI, Wisconnsin, 5, -6:1, 39532, 1148, 1, 0, 0 +46005,Beadle County, SD, South Dakota, 5, -6:1, 17183, 1148, 1, 0, 0 +39097,Madison County, OH, Ohio, 4, -5:1, 41576, 1387, 1, 0, 0 +48239,Jackson County, TX, Texas, 7, -6:1, 13685, 1148, 1, 0, 0 +12097,Osceola County, FL, Florida, 3, -5:1, 145666, 1387, 1, 0, 0 +37105,Lee County, NC, North Carolina, 2, -5:1, 49328, 1387, 1, 0, 0 +48371,Pecos County, TX, Texas, 7, -6:1, 16003, 1148, 1, 0, 0 +51810,Virginia Beach city, VA, Virginia, 2, -5:1, 432380, 1387, 1, 0, 0 +40065,Jackson County, OK, Oklahoma, 7, -6:1, 28771, 1148, 1, 0, 0 +13155,Irwin County, GA, Georgia, 3, -5:1, 8982, 1387, 1, 0, 0 +20101,Lane County, KS, Kansas, 6, -6:1, 2264, 1148, 1, 0, 0 +40017,Canadian County, OK, Oklahoma, 7, -6:1, 85463, 1148, 1, 0, 0 +19073,Greene County, IA, Iowa, 5, -6:1, 10065, 1148, 1, 0, 0 +25013,Hampden County, MA, Massachusetts, 0, -5:1, 439609, 1387, 1, 0, 0 +04025,Yavapai County, AZ, Arizona, 8, -7:1, 148511, 4647, 1, 0, 0 +47113,Madison County, TN, Tennesee, 3, -6:1, 85954, 1148, 1, 0, 0 +18175,Washington County, IN, Indiana, 4, -5:1, 27900, 1387, 1, 0, 0 +55005,Barron County, WI, Wisconnsin, 5, -6:1, 43872, 1148, 1, 0, 0 +17109,McDonough County, IL, Illinois, 6, -6:1, 33917, 1148, 1, 0, 0 +17113,McLean County, IL, Illinois, 6, -6:1, 142652, 1148, 1, 0, 0 +13033,Burke County, GA, Georgia, 3, -5:1, 22854, 1387, 1, 0, 0 +04023,Santa Cruz County, AZ, Arizona, 8, -7:1, 38116, 4647, 1, 0, 0 +54067,Nicholas County, WV, West Virginia, 2, -5:1, 27595, 1387, 1, 0, 0 +19135,Monroe County, IA, Iowa, 5, -6:1, 8041, 1148, 1, 0, 0 +21083,Graves County, KY, Kentucky, 4, -6:1, 35847, 1148, 1, 0, 0 +38067,Pembina County, ND, North Dakota, 5, -6:1, 8485, 1148, 1, 0, 0 +19031,Cedar County, IA, Iowa, 5, -6:1, 17977, 1148, 1, 0, 0 +21059,Daviess County, KY, Kentucky, 4, -6:1, 91139, 1148, 1, 0, 0 +51580,Covington city, VA, Virginia, 2, -5:1, 6857, 1387, 1, 0, 0 +42045,Delaware County, PA, Pennsylvania, 1, -5:1, 542593, 1387, 1, 0, 0 +05009,Boone County, AR, Arkansas, 7, -6:1, 31872, 1148, 1, 0, 0 +42103,Pike County, PA, Pennsylvania, 1, -5:1, 40172, 1387, 1, 0, 0 +40151,Woods County, OK, Oklahoma, 7, -6:1, 8366, 1148, 1, 0, 0 +13311,White County, GA, Georgia, 3, -5:1, 17457, 1387, 1, 0, 0 +22065,Madison Parish, LA, Louisiana, 7, -6:1, 12808, 1148, 1, 0, 0 +55091,Pepin County, WI, Wisconnsin, 5, -6:1, 7118, 1148, 1, 0, 0 +28047,Harrison County, MS, Missippi, 5, -6:1, 177981, 1148, 1, 0, 0 +38105,Williams County, ND, North Dakota, 5, -6:1, 20150, 1148, 1, 0, 0 +12093,Okeechobee County, FL, Florida, 3, -5:1, 31158, 1387, 1, 0, 0 +27043,Faribault County, MN, Minnesota, 5, -6:1, 16244, 1148, 1, 0, 0 +29027,Callaway County, MO, Mosourri, 6, -6:1, 37437, 1148, 1, 0, 0 +20081,Haskell County, KS, Kansas, 6, -6:1, 3976, 1148, 1, 0, 0 +06067,Sacramento County, CA, California, 9, -8:1, 1144202, 3240, 0, 1, 0 +20011,Bourbon County, KS, Kansas, 6, -6:1, 15260, 1148, 1, 0, 0 +21129,Lee County, KY, Kentucky, 4, -5:1, 8021, 1387, 1, 0, 0 +40101,Muskogee County, OK, Oklahoma, 7, -6:1, 70004, 1148, 1, 0, 0 +35045,San Juan County, NM, New Mexico, 8, -7:1, 106020, 4647, 1, 0, 0 +39021,Champaign County, OH, Ohio, 4, -5:1, 38182, 1387, 1, 0, 0 +51045,Craig County, VA, Virginia, 2, -5:1, 4882, 1387, 1, 0, 0 +40123,Pontotoc County, OK, Oklahoma, 7, -6:1, 34591, 1148, 1, 0, 0 +48457,Tyler County, TX, Texas, 7, -6:1, 20408, 1148, 1, 0, 0 +39077,Huron County, OH, Ohio, 4, -5:1, 60293, 1387, 1, 0, 0 +28141,Tishomingo County, MS, Missippi, 5, -6:1, 18654, 1148, 1, 0, 0 +21171,Monroe County, KY, Kentucky, 4, -5:1, 11201, 1387, 1, 0, 0 +20025,Clark County, KS, Kansas, 6, -6:1, 2361, 1148, 1, 0, 0 +30083,Richland County, MT, Montana, 6, -7:1, 10105, 4647, 1, 0, 0 +45003,Aiken County, SC, South Carolina, 2, -5:1, 134051, 1387, 1, 0, 0 +31113,Logan County, NE, Nebraska, 6, -7:1, 880, 4647, 1, 0, 0 +51670,Hopewell city, VA, Virginia, 2, -5:1, 22529, 1387, 1, 0, 0 +38053,McKenzie County, ND, North Dakota, 5, -6:1, 5682, 1148, 1, 0, 0 +08075,Logan County, CO, Colorado, 8, -7:1, 17890, 4647, 1, 0, 0 +17069,Hardin County, IL, Illinois, 6, -6:1, 4902, 1148, 1, 0, 0 +02170,Matanuska-Susitna Borough, AK, Alaska, 9, -9:1, 56258, 1174, 0, 0, 1 +28117,Prentiss County, MS, Missippi, 5, -6:1, 24295, 1148, 1, 0, 0 +31073,Gosper County, NE, Nebraska, 6, -6:1, 2329, 1148, 1, 0, 0 +48101,Cottle County, TX, Texas, 7, -6:1, 1922, 1148, 1, 0, 0 +05011,Bradley County, AR, Arkansas, 7, -6:1, 11433, 1148, 1, 0, 0 +40147,Washington County, OK, Oklahoma, 7, -6:1, 47519, 1148, 1, 0, 0 +29063,DeKalb County, MO, Mosourri, 6, -6:1, 11129, 1148, 1, 0, 0 +39109,Miami County, OH, Ohio, 4, -5:1, 98147, 1387, 1, 0, 0 +05025,Cleveland County, AR, Arkansas, 7, -6:1, 8452, 1148, 1, 0, 0 +05041,Desha County, AR, Arkansas, 7, -6:1, 15110, 1148, 1, 0, 0 +18083,Knox County, IN, Indiana, 4, -5:1, 39388, 1387, 1, 0, 0 +38005,Benson County, ND, North Dakota, 5, -6:1, 6893, 1148, 1, 0, 0 +45055,Kershaw County, SC, South Carolina, 2, -5:1, 48593, 1387, 1, 0, 0 +47095,Lake County, TN, Tennesee, 3, -6:1, 8171, 1148, 1, 0, 0 +08101,Pueblo County, CO, Colorado, 8, -7:1, 134867, 4647, 1, 0, 0 +01077,Lauderdale County, AL, Alabama, 3, -6:1, 84325, 1148, 1, 0, 0 +30097,Sweet Grass County, MT, Montana, 6, -7:1, 3407, 4647, 1, 0, 0 +31043,Dakota County, NE, Nebraska, 6, -6:1, 18792, 1148, 1, 0, 0 +48301,Loving County, TX, Texas, 7, -6:1, 114, 1148, 1, 0, 0 +22025,Catahoula Parish, LA, Louisiana, 7, -6:1, 11064, 1148, 1, 0, 0 +02016,Aleutians West Census Area, AK, Alaska, 9, -9:1, 3879, 1174, 0, 0, 1 +30079,Prairie County, MT, Montana, 6, -7:1, 1333, 4647, 1, 0, 0 +31135,Perkins County, NE, Nebraska, 6, -7:1, 3171, 4647, 1, 0, 0 +06039,Madera County, CA, California, 9, -8:1, 114748, 3240, 0, 1, 0 +18119,Owen County, IN, Indiana, 4, -5:1, 20419, 1387, 1, 0, 0 +21149,McLean County, KY, Kentucky, 4, -5:1, 9845, 1387, 1, 0, 0 +12119,Sumter County, FL, Florida, 3, -5:1, 40426, 1387, 1, 0, 0 +17181,Union County, IL, Illinois, 6, -6:1, 17996, 1148, 1, 0, 0 +28149,Warren County, MS, Missippi, 5, -6:1, 49404, 1148, 1, 0, 0 +22121,West Baton Rouge Parish, LA, Louisiana, 7, -6:1, 20683, 1148, 1, 0, 0 +06001,Alameda County, CA, California, 9, -8:1, 1400322, 3240, 0, 1, 0 +18019,Clark County, IN, Indiana, 4, -5:1, 93805, 1387, 1, 0, 0 +27173,Yellow Medicine County, MN, Minnesota, 5, -6:1, 11416, 1148, 1, 0, 0 +17013,Calhoun County, IL, Illinois, 6, -6:1, 4981, 1148, 1, 0, 0 +01017,Chambers County, AL, Alabama, 3, -6:1, 36713, 1148, 1, 0, 0 +48131,Duval County, TX, Texas, 7, -6:1, 13662, 1148, 1, 0, 0 +06051,Mono County, CA, California, 9, -8:1, 10288, 3240, 0, 1, 0 +35003,Catron County, NM, New Mexico, 8, -7:1, 2845, 4647, 1, 0, 0 +05129,Searcy County, AR, Arkansas, 7, -6:1, 7761, 1148, 1, 0, 0 +55097,Portage County, WI, Wisconnsin, 5, -6:1, 64752, 1148, 1, 0, 0 +21157,Marshall County, KY, Kentucky, 4, -5:1, 30312, 1387, 1, 0, 0 +31053,Dodge County, NE, Nebraska, 6, -6:1, 35333, 1148, 1, 0, 0 +40063,Hughes County, OK, Oklahoma, 7, -6:1, 14081, 1148, 1, 0, 0 +05019,Clark County, AR, Arkansas, 7, -6:1, 21933, 1148, 1, 0, 0 +41031,Jefferson County, OR, Oregon, 9, -8:1, 16627, 3240, 0, 1, 0 +23021,Piscataquis County, ME, Maine, 0, -5:1, 18282, 1387, 1, 0, 0 +13119,Franklin County, GA, Georgia, 3, -5:1, 19080, 1387, 1, 0, 0 +31165,Sioux County, NE, Nebraska, 6, -7:1, 1486, 4647, 1, 0, 0 +29177,Ray County, MO, Mosourri, 6, -6:1, 23708, 1148, 1, 0, 0 +23011,Kennebec County, ME, Maine, 0, -5:1, 115207, 1387, 1, 0, 0 +27025,Chisago County, MN, Minnesota, 5, -6:1, 40852, 1148, 1, 0, 0 +40075,Kiowa County, OK, Oklahoma, 7, -6:1, 10613, 1148, 1, 0, 0 +55085,Oneida County, WI, Wisconnsin, 5, -6:1, 35672, 1148, 1, 0, 0 +19113,Linn County, IA, Iowa, 5, -6:1, 182651, 1148, 1, 0, 0 +53039,Klickitat County, WA, Washiington, 9, -8:1, 19295, 3240, 0, 1, 0 +54023,Grant County, WV, West Virginia, 2, -5:1, 11098, 1387, 1, 0, 0 +38055,McLean County, ND, North Dakota, 5, -6:1, 9704, 1148, 1, 0, 0 +01023,Choctaw County, AL, Alabama, 3, -6:1, 15917, 1148, 1, 0, 0 +21051,Clay County, KY, Kentucky, 4, -6:1, 22799, 1148, 1, 0, 0 +09005,Litchfield County, CT, Connecticut, 0, -5:1, 181277, 1387, 1, 0, 0 +40025,Cimarron County, OK, Oklahoma, 7, -6:1, 2959, 1148, 1, 0, 0 +18015,Carroll County, IN, Indiana, 4, -5:1, 20010, 1387, 1, 0, 0 +21119,Knott County, KY, Kentucky, 4, -5:1, 17989, 1387, 1, 0, 0 +17077,Jackson County, IL, Illinois, 6, -6:1, 60410, 1148, 1, 0, 0 +47075,Haywood County, TN, Tennesee, 3, -5:1, 19525, 1387, 1, 0, 0 +53029,Island County, WA, Washiington, 9, -8:1, 70319, 3240, 0, 1, 0 +35019,Guadalupe County, NM, New Mexico, 8, -7:1, 4050, 4647, 1, 0, 0 +31047,Dawson County, NE, Nebraska, 6, -6:1, 23183, 1148, 1, 0, 0 +35061,Valencia County, NM, New Mexico, 8, -7:1, 64626, 4647, 1, 0, 0 +27053,Hennepin County, MN, Minnesota, 5, -6:1, 1059669, 1148, 1, 0, 0 +39151,Stark County, OH, Ohio, 4, -5:1, 373112, 1387, 1, 0, 0 +36015,Chemung County, NY, New York, 1, -5:1, 92021, 1387, 1, 0, 0 +37011,Avery County, NC, North Carolina, 2, -5:1, 15742, 1387, 1, 0, 0 +47179,Washington County, TN, Tennesee, 3, -6:1, 102211, 1148, 1, 0, 0 +05075,Lawrence County, AR, Arkansas, 7, -6:1, 17304, 1148, 1, 0, 0 +26097,Mackinac County, MI, Michigan, 4, -5:1, 11097, 1387, 1, 0, 0 +54031,Hardy County, WV, West Virginia, 2, -5:1, 11829, 1387, 1, 0, 0 +45049,Hampton County, SC, South Carolina, 2, -5:1, 19200, 1387, 1, 0, 0 +47085,Humphreys County, TN, Tennesee, 3, -5:1, 17059, 1387, 1, 0, 0 +28075,Lauderdale County, MS, Missippi, 5, -6:1, 76143, 1148, 1, 0, 0 +48129,Donley County, TX, Texas, 7, -6:1, 3822, 1148, 1, 0, 0 +09001,Fairfield County, CT, Connecticut, 0, -5:1, 838362, 1148, 1, 0, 0 +39107,Mercer County, OH, Ohio, 4, -5:1, 41198, 1387, 1, 0, 0 +31045,Dawes County, NE, Nebraska, 6, -6:1, 8979, 1148, 1, 0, 0 +38061,Mountrail County, ND, North Dakota, 5, -6:1, 6633, 1148, 1, 0, 0 +31167,Stanton County, NE, Nebraska, 6, -7:1, 6215, 4647, 1, 0, 0 +26115,Monroe County, MI, Michigan, 4, -5:1, 143499, 1387, 1, 0, 0 +21231,Wayne County, KY, Kentucky, 4, -5:1, 19107, 1387, 1, 0, 0 +29057,Dade County, MO, Mosourri, 6, -6:1, 7892, 1148, 1, 0, 0 +51149,Prince George County, VA, Virginia, 2, -5:1, 30135, 1387, 1, 0, 0 +12019,Clay County, FL, Florida, 3, -5:1, 137455, 1387, 1, 0, 0 +18101,Martin County, IN, Indiana, 4, -5:1, 10531, 1387, 1, 0, 0 +49041,Sevier County, UT, Utah, 8, -7:1, 18452, 4647, 1, 0, 0 +22059,La Salle Parish, LA, Louisiana, 7, -6:1, 13661, 1148, 1, 0, 0 +27027,Clay County, MN, Minnesota, 5, -6:1, 51599, 1148, 1, 0, 0 +22077,Pointe Coupee Parish, LA, Louisiana, 7, -6:1, 23565, 1148, 1, 0, 0 +17103,Lee County, IL, Illinois, 6, -6:1, 36021, 1148, 1, 0, 0 +01047,Dallas County, AL, Alabama, 3, -6:1, 46768, 1148, 1, 0, 0 +47135,Perry County, TN, Tennesee, 3, -6:1, 7508, 1148, 1, 0, 0 +05107,Phillips County, AR, Arkansas, 7, -6:1, 27363, 1148, 1, 0, 0 +37107,Lenoir County, NC, North Carolina, 2, -5:1, 59046, 1387, 1, 0, 0 +28001,Adams County, MS, Missippi, 5, -6:1, 34225, 1148, 1, 0, 0 +21117,Kenton County, KY, Kentucky, 4, -5:1, 146732, 1387, 1, 0, 0 +48057,Calhoun County, TX, Texas, 7, -6:1, 20596, 1148, 1, 0, 0 +46079,Lake County, SD, South Dakota, 5, -7:1, 11139, 4647, 1, 0, 0 +48345,Motley County, TX, Texas, 7, -6:1, 1305, 1148, 1, 0, 0 +10001,Kent County, DE, Delaware, 1, -5:1, 124089, 1387, 1, 0, 0 +39013,Belmont County, OH, Ohio, 4, -5:1, 69175, 1387, 1, 0, 0 +29197,Schuyler County, MO, Mosourri, 6, -6:1, 4443, 1148, 1, 0, 0 +46017,Buffalo County, SD, South Dakota, 5, -6:1, 1738, 1148, 1, 0, 0 +13303,Washington County, GA, Georgia, 3, -5:1, 20033, 1387, 1, 0, 0 +47007,Bledsoe County, TN, Tennesee, 3, -5:1, 10795, 1387, 1, 0, 0 +26083,Keweenaw County, MI, Michigan, 4, -5:1, 2077, 1387, 1, 0, 0 +48411,San Saba County, TX, Texas, 7, -6:1, 5615, 1148, 1, 0, 0 +51515,Bedford city, VA, Virginia, 2, -5:1, 6317, 1387, 1, 0, 0 +48399,Runnels County, TX, Texas, 7, -6:1, 11507, 1148, 1, 0, 0 +51760,Richmond city, VA, Virginia, 2, -5:1, 194173, 1387, 1, 0, 0 +31063,Frontier County, NE, Nebraska, 6, -6:1, 3082, 1148, 1, 0, 0 +31033,Cheyenne County, NE, Nebraska, 6, -6:1, 9476, 1148, 1, 0, 0 +28039,George County, MS, Missippi, 5, -6:1, 19645, 1148, 1, 0, 0 +29055,Crawford County, MO, Mosourri, 6, -6:1, 22165, 1148, 1, 0, 0 +48179,Gray County, TX, Texas, 7, -6:1, 23603, 1148, 1, 0, 0 +48337,Montague County, TX, Texas, 7, -6:1, 18539, 1148, 1, 0, 0 +31109,Lancaster County, NE, Nebraska, 6, -7:1, 235589, 4647, 1, 0, 0 +13099,Early County, GA, Georgia, 3, -5:1, 12197, 1387, 1, 0, 0 +29201,Scott County, MO, Mosourri, 6, -6:1, 40262, 1148, 1, 0, 0 +22017,Caddo Parish, LA, Louisiana, 7, -6:1, 242471, 1148, 1, 0, 0 +48139,Ellis County, TX, Texas, 7, -6:1, 103638, 1148, 1, 0, 0 +48175,Goliad County, TX, Texas, 7, -6:1, 6998, 1148, 1, 0, 0 +36075,Oswego County, NY, New York, 1, -5:1, 124006, 1387, 1, 0, 0 +21041,Carroll County, KY, Kentucky, 4, -6:1, 9603, 1148, 1, 0, 0 +48405,San Augustine County, TX, Texas, 7, -6:1, 8086, 1148, 1, 0, 0 +17043,DuPage County, IL, Illinois, 6, -6:1, 880491, 1148, 1, 0, 0 +27163,Washington County, MN, Minnesota, 5, -6:1, 196486, 1148, 1, 0, 0 +46103,Pennington County, SD, South Dakota, 5, -7:1, 87702, 4647, 1, 0, 0 +48389,Reeves County, TX, Texas, 7, -6:1, 14478, 1148, 1, 0, 0 +39041,Delaware County, OH, Ohio, 4, -5:1, 92209, 1387, 1, 0, 0 +48425,Somervell County, TX, Texas, 7, -6:1, 6421, 1148, 1, 0, 0 +21101,Henderson County, KY, Kentucky, 4, -6:1, 44457, 1148, 1, 0, 0 +37119,Mecklenburg County, NC, North Carolina, 2, -5:1, 630848, 1387, 1, 0, 0 +31069,Garden County, NE, Nebraska, 6, -6:1, 2138, 1148, 1, 0, 0 +42051,Fayette County, PA, Pennsylvania, 1, -5:1, 144847, 1387, 1, 0, 0 +36025,Delaware County, NY, New York, 1, -5:1, 46086, 1387, 1, 0, 0 +28071,Lafayette County, MS, Missippi, 5, -6:1, 34555, 1148, 1, 0, 0 +48201,Harris County, TX, Texas, 7, -6:1, 3206063, 1148, 1, 0, 0 +28045,Hancock County, MS, Missippi, 5, -6:1, 40327, 1148, 1, 0, 0 +37197,Yadkin County, NC, North Carolina, 2, -5:1, 34955, 1387, 1, 0, 0 +19173,Taylor County, IA, Iowa, 5, -6:1, 7153, 1148, 1, 0, 0 +13223,Paulding County, GA, Georgia, 3, -5:1, 73534, 1387, 1, 0, 0 +47155,Sevier County, TN, Tennesee, 3, -6:1, 64505, 1148, 1, 0, 0 +34003,Bergen County, NJ, New Jersey, 0, -5:1, 858529, 1387, 1, 0, 0 +05071,Johnson County, AR, Arkansas, 7, -6:1, 21403, 1148, 1, 0, 0 +48295,Lipscomb County, TX, Texas, 7, -6:1, 2973, 1148, 1, 0, 0 +45079,Richland County, SC, South Carolina, 2, -5:1, 307056, 1387, 1, 0, 0 +30109,Wibaux County, MT, Montana, 6, -7:1, 1148, 4647, 1, 0, 0 +51735,Poquoson city, VA, Virginia, 2, -5:1, 11455, 1387, 1, 0, 0 +17083,Jersey County, IL, Illinois, 6, -6:1, 21373, 1148, 1, 0, 0 +45029,Colleton County, SC, South Carolina, 2, -5:1, 37364, 1387, 1, 0, 0 +21105,Hickman County, KY, Kentucky, 4, -6:1, 5247, 1148, 1, 0, 0 +45031,Darlington County, SC, South Carolina, 2, -5:1, 66366, 1387, 1, 0, 0 +20047,Edwards County, KS, Kansas, 6, -6:1, 3312, 1148, 1, 0, 0 +17179,Tazewell County, IL, Illinois, 6, -6:1, 127958, 1148, 1, 0, 0 +08095,Phillips County, CO, Colorado, 8, -7:1, 4325, 4647, 1, 0, 0 +51683,Manassas city, VA, Virginia, 2, -5:1, 35336, 1387, 1, 0, 0 +27013,Blue Earth County, MN, Minnesota, 5, -6:1, 53767, 1148, 1, 0, 0 +53057,Skagit County, WA, Washiington, 9, -8:1, 99357, 3240, 0, 1, 0 +19081,Hancock County, IA, Iowa, 5, -6:1, 12044, 1148, 1, 0, 0 +13131,Grady County, GA, Georgia, 3, -5:1, 21501, 1387, 1, 0, 0 +48379,Rains County, TX, Texas, 7, -6:1, 8618, 1148, 1, 0, 0 +37117,Martin County, NC, North Carolina, 2, -5:1, 26192, 1387, 1, 0, 0 +21233,Webster County, KY, Kentucky, 4, -5:1, 13482, 1387, 1, 0, 0 +37081,Guilford County, NC, North Carolina, 2, -5:1, 387722, 1387, 1, 0, 0 +08107,Routt County, CO, Colorado, 8, -7:1, 17514, 4647, 1, 0, 0 +21191,Pendleton County, KY, Kentucky, 4, -5:1, 13703, 1387, 1, 0, 0 +21115,Johnson County, KY, Kentucky, 4, -6:1, 24022, 1148, 1, 0, 0 +27099,Mower County, MN, Minnesota, 5, -6:1, 37039, 1148, 1, 0, 0 +08121,Washington County, CO, Colorado, 8, -7:1, 4576, 4647, 1, 0, 0 +08007,Archuleta County, CO, Colorado, 8, -7:1, 9113, 4647, 1, 0, 0 +36099,Seneca County, NY, New York, 1, -5:1, 31943, 1387, 1, 0, 0 +51065,Fluvanna County, VA, Virginia, 2, -5:1, 18575, 1387, 1, 0, 0 +37087,Haywood County, NC, North Carolina, 2, -5:1, 51422, 1387, 1, 0, 0 +55049,Iowa County, WI, Wisconnsin, 5, -6:1, 22422, 1148, 1, 0, 0 +27121,Pope County, MN, Minnesota, 5, -6:1, 10886, 1148, 1, 0, 0 +46113,Shannon County, SD, South Dakota, 5, -7:1, 12183, 4647, 1, 0, 0 +18117,Orange County, IN, Indiana, 4, -5:1, 19606, 1387, 1, 0, 0 +46053,Gregory County, SD, South Dakota, 5, -6:1, 4948, 1148, 1, 0, 0 +47021,Cheatham County, TN, Tennesee, 3, -5:1, 35344, 1387, 1, 0, 0 +08117,Summit County, CO, Colorado, 8, -7:1, 18749, 4647, 1, 0, 0 +31003,Antelope County, NE, Nebraska, 6, -6:1, 7181, 1148, 1, 0, 0 +46045,Edmunds County, SD, South Dakota, 5, -6:1, 4219, 1148, 1, 0, 0 +45051,Horry County, SC, South Carolina, 2, -5:1, 174762, 1387, 1, 0, 0 +27039,Dodge County, MN, Minnesota, 5, -6:1, 17209, 1148, 1, 0, 0 +42015,Bradford County, PA, Pennsylvania, 1, -5:1, 62459, 1387, 1, 0, 0 +19025,Calhoun County, IA, Iowa, 5, -6:1, 11378, 1148, 1, 0, 0 +47119,Maury County, TN, Tennesee, 3, -6:1, 69633, 1148, 1, 0, 0 +48089,Colorado County, TX, Texas, 7, -6:1, 19021, 1148, 1, 0, 0 +28147,Walthall County, MS, Missippi, 5, -6:1, 14369, 1148, 1, 0, 0 +05059,Hot Spring County, AR, Arkansas, 7, -6:1, 29035, 1148, 1, 0, 0 +37059,Davie County, NC, North Carolina, 2, -5:1, 32095, 1387, 1, 0, 0 +26055,Grand Traverse County, MI, Michigan, 4, -5:1, 74134, 1387, 1, 0, 0 +51001,Accomack County, VA, Virginia, 2, -5:1, 32245, 1387, 1, 0, 0 +51036,Charles City County, VA, Virginia, 2, -5:1, 7092, 1387, 1, 0, 0 +30023,Deer Lodge County, MT, Montana, 6, -7:1, 9999, 4647, 1, 0, 0 +48023,Baylor County, TX, Texas, 7, -6:1, 4152, 1148, 1, 0, 0 +22021,Caldwell Parish, LA, Louisiana, 7, -6:1, 10364, 1148, 1, 0, 0 +25005,Bristol County, MA, Massachusetts, 0, -5:1, 517543, 1387, 1, 0, 0 +12037,Franklin County, FL, Florida, 3, -5:1, 10079, 1387, 1, 0, 0 +28163,Yazoo County, MS, Missippi, 5, -6:1, 25510, 1148, 1, 0, 0 +01049,DeKalb County, AL, Alabama, 3, -6:1, 58454, 1148, 1, 0, 0 +51041,Chesterfield County, VA, Virginia, 2, -5:1, 245915, 1387, 1, 0, 0 +22023,Cameron Parish, LA, Louisiana, 7, -6:1, 9063, 1148, 1, 0, 0 +20185,Stafford County, KS, Kansas, 6, -6:1, 5000, 1148, 1, 0, 0 +30015,Chouteau County, MT, Montana, 6, -7:1, 5187, 4647, 1, 0, 0 +48039,Brazoria County, TX, Texas, 7, -6:1, 230335, 1148, 1, 0, 0 +10005,Sussex County, DE, Delaware, 1, -5:1, 136707, 1387, 1, 0, 0 +56025,Natrona County, WY, Wyoming, 8, -7:1, 63341, 4647, 1, 0, 0 +48017,Bailey County, TX, Texas, 7, -6:1, 6907, 1148, 1, 0, 0 +19033,Cerro Gordo County, IA, Iowa, 5, -6:1, 46159, 1148, 1, 0, 0 +35007,Colfax County, NM, New Mexico, 8, -7:1, 13597, 4647, 1, 0, 0 +42031,Clarion County, PA, Pennsylvania, 1, -5:1, 41841, 1387, 1, 0, 0 +39029,Columbiana County, OH, Ohio, 4, -5:1, 111521, 1387, 1, 0, 0 +36001,Albany County, NY, New York, 1, -5:1, 292586, 1387, 1, 0, 0 +27033,Cottonwood County, MN, Minnesota, 5, -6:1, 12045, 1148, 1, 0, 0 +42115,Susquehanna County, PA, Pennsylvania, 1, -5:1, 42144, 1387, 1, 0, 0 +38081,Sargent County, ND, North Dakota, 5, -6:1, 4457, 1148, 1, 0, 0 +48147,Fannin County, TX, Texas, 7, -6:1, 28129, 1148, 1, 0, 0 +24003,Anne Arundel County, MD, Maryland, 2, -5:1, 476060, 1387, 1, 0, 0 +22085,Sabine Parish, LA, Louisiana, 7, -6:1, 23824, 1148, 1, 0, 0 +27171,Wright County, MN, Minnesota, 5, -6:1, 85123, 1148, 1, 0, 0 +01117,Shelby County, AL, Alabama, 3, -6:1, 140715, 1148, 1, 0, 0 +29135,Moniteau County, MO, Mosourri, 6, -6:1, 13263, 1148, 1, 0, 0 +18069,Huntington County, IN, Indiana, 4, -5:1, 37259, 1387, 1, 0, 0 +28085,Lincoln County, MS, Missippi, 5, -6:1, 31771, 1148, 1, 0, 0 +35055,Taos County, NM, New Mexico, 8, -7:1, 26815, 4647, 1, 0, 0 +12011,Broward County, FL, Florida, 3, -5:1, 1503407, 1387, 1, 0, 0 +51077,Grayson County, VA, Virginia, 2, -5:1, 16118, 1387, 1, 0, 0 +29203,Shannon County, MO, Mosourri, 6, -6:1, 8252, 1148, 1, 0, 0 +48171,Gillespie County, TX, Texas, 7, -6:1, 20045, 1148, 1, 0, 0 +17131,Mercer County, IL, Illinois, 6, -6:1, 17640, 1148, 1, 0, 0 +40041,Delaware County, OK, Oklahoma, 7, -6:1, 34154, 1148, 1, 0, 0 +18065,Henry County, IN, Indiana, 4, -5:1, 48785, 1387, 1, 0, 0 +54037,Jefferson County, WV, West Virginia, 2, -5:1, 41368, 1387, 1, 0, 0 +18135,Randolph County, IN, Indiana, 4, -5:1, 27628, 1387, 1, 0, 0 +28121,Rankin County, MS, Missippi, 5, -6:1, 109613, 1148, 1, 0, 0 +45041,Florence County, SC, South Carolina, 2, -5:1, 124904, 1387, 1, 0, 0 +48431,Sterling County, TX, Texas, 7, -6:1, 1364, 1148, 1, 0, 0 +29091,Howell County, MO, Mosourri, 6, -6:1, 35776, 1148, 1, 0, 0 +37199,Yancey County, NC, North Carolina, 2, -5:1, 16607, 1387, 1, 0, 0 +38065,Oliver County, ND, North Dakota, 5, -6:1, 2215, 1148, 1, 0, 0 +46105,Perkins County, SD, South Dakota, 5, -7:1, 3505, 4647, 1, 0, 0 +13015,Bartow County, GA, Georgia, 3, -5:1, 71929, 1387, 1, 0, 0 +51175,Southampton County, VA, Virginia, 2, -5:1, 17450, 1387, 1, 0, 0 +29017,Bollinger County, MO, Mosourri, 6, -6:1, 11513, 1148, 1, 0, 0 +31097,Johnson County, NE, Nebraska, 6, -7:1, 4564, 4647, 1, 0, 0 +31039,Cuming County, NE, Nebraska, 6, -6:1, 9993, 1148, 1, 0, 0 +21125,Laurel County, KY, Kentucky, 4, -5:1, 50734, 1387, 1, 0, 0 +38001,Adams County, ND, North Dakota, 5, -6:1, 2714, 1148, 1, 0, 0 +39047,Fayette County, OH, Ohio, 4, -5:1, 28493, 1387, 1, 0, 0 +06057,Nevada County, CA, California, 9, -8:1, 91334, 3240, 0, 1, 0 +20001,Allen County, KS, Kansas, 6, -6:1, 14556, 1148, 1, 0, 0 +48353,Nolan County, TX, Texas, 7, -6:1, 16501, 1148, 1, 0, 0 +13211,Morgan County, GA, Georgia, 3, -5:1, 15091, 1387, 1, 0, 0 +08015,Chaffee County, CO, Colorado, 8, -7:1, 15075, 4647, 1, 0, 0 +39007,Ashtabula County, OH, Ohio, 4, -5:1, 103300, 1387, 1, 0, 0 +28107,Panola County, MS, Missippi, 5, -6:1, 33400, 1148, 1, 0, 0 +56005,Campbell County, WY, Wyoming, 8, -7:1, 32465, 4647, 1, 0, 0 +12055,Highlands County, FL, Florida, 3, -5:1, 75206, 1387, 1, 0, 0 +45057,Lancaster County, SC, South Carolina, 2, -5:1, 58887, 1387, 1, 0, 0 +39119,Muskingum County, OH, Ohio, 4, -5:1, 84470, 1387, 1, 0, 0 +31015,Boyd County, NE, Nebraska, 6, -6:1, 2565, 1148, 1, 0, 0 +19001,Adair County, IA, Iowa, 5, -6:1, 8064, 1387, 1, 0, 0 +25007,Dukes County, MA, Massachusetts, 0, -5:1, 13888, 1387, 1, 0, 0 +26141,Presque Isle County, MI, Michigan, 4, -5:1, 14424, 1387, 1, 0, 0 +31141,Platte County, NE, Nebraska, 6, -7:1, 30737, 4647, 1, 0, 0 +26139,Ottawa County, MI, Michigan, 4, -5:1, 224357, 1387, 1, 0, 0 +08113,San Miguel County, CO, Colorado, 8, -7:1, 5437, 4647, 1, 0, 0 +06021,Glenn County, CA, California, 9, -8:1, 26234, 3240, 0, 1, 0 +23029,Washington County, ME, Maine, 0, -5:1, 35502, 1387, 1, 0, 0 +56039,Teton County, WY, Wyoming, 8, -7:1, 14163, 4647, 1, 0, 0 +51097,King and Queen County, VA, Virginia, 2, -5:1, 6529, 1387, 1, 0, 0 +02220,Sitka Borough, AK, Alaska, 9, -9:1, 8338, 1174, 0, 0, 1 +41057,Tillamook County, OR, Oregon, 9, -8:1, 24356, 3240, 0, 1, 0 +35005,Chaves County, NM, New Mexico, 8, -7:1, 62505, 4647, 1, 0, 0 +39057,Greene County, OH, Ohio, 4, -5:1, 146607, 1387, 1, 0, 0 +47053,Gibson County, TN, Tennesee, 3, -5:1, 48186, 1387, 1, 0, 0 +30049,Lewis and Clark County, MT, Montana, 6, -7:1, 53655, 4647, 1, 0, 0 +20089,Jewell County, KS, Kansas, 6, -6:1, 3867, 1148, 1, 0, 0 +05043,Drew County, AR, Arkansas, 7, -6:1, 17575, 1148, 1, 0, 0 +24021,Frederick County, MD, Maryland, 2, -5:1, 186777, 1387, 1, 0, 0 +17061,Greene County, IL, Illinois, 6, -6:1, 15549, 1148, 1, 0, 0 +55017,Chippewa County, WI, Wisconnsin, 5, -6:1, 54574, 1148, 1, 0, 0 +48235,Irion County, TX, Texas, 7, -6:1, 1739, 1148, 1, 0, 0 +47111,Macon County, TN, Tennesee, 3, -6:1, 18181, 1148, 1, 0, 0 +18077,Jefferson County, IN, Indiana, 4, -5:1, 31466, 1387, 1, 0, 0 +47177,Warren County, TN, Tennesee, 3, -6:1, 36160, 1148, 1, 0, 0 +16017,Bonner County, ID, Idaho, 8, -7:1, 35226, 4647, 1, 0, 0 +35057,Torrance County, NM, New Mexico, 8, -7:1, 15433, 4647, 1, 0, 0 +26153,Schoolcraft County, MI, Michigan, 4, -5:1, 8805, 1387, 1, 0, 0 +05073,Lafayette County, AR, Arkansas, 7, -6:1, 8926, 1148, 1, 0, 0 +49053,Washington County, UT, Utah, 8, -7:1, 82115, 4647, 1, 0, 0 +51067,Franklin County, VA, Virginia, 2, -5:1, 44538, 1387, 1, 0, 0 +17105,Livingston County, IL, Illinois, 6, -6:1, 39702, 1148, 1, 0, 0 +08021,Conejos County, CO, Colorado, 8, -7:1, 7972, 4647, 1, 0, 0 +31079,Hall County, NE, Nebraska, 6, -6:1, 51851, 1148, 1, 0, 0 +35049,Santa Fe County, NM, New Mexico, 8, -7:1, 123386, 4647, 1, 0, 0 +48497,Wise County, TX, Texas, 7, -6:1, 44135, 1148, 1, 0, 0 +48397,Rockwall County, TX, Texas, 7, -6:1, 37174, 1148, 1, 0, 0 +20053,Ellsworth County, KS, Kansas, 6, -6:1, 6285, 1148, 1, 0, 0 +22107,Tensas Parish, LA, Louisiana, 7, -6:1, 6631, 1148, 1, 0, 0 +31035,Clay County, NE, Nebraska, 6, -6:1, 7147, 1148, 1, 0, 0 +19119,Lyon County, IA, Iowa, 5, -6:1, 12012, 1148, 1, 0, 0 +28053,Humphreys County, MS, Missippi, 5, -6:1, 11344, 1148, 1, 0, 0 +19131,Mitchell County, IA, Iowa, 5, -6:1, 11012, 1148, 1, 0, 0 +37039,Cherokee County, NC, North Carolina, 2, -5:1, 22758, 1387, 1, 0, 0 +31139,Pierce County, NE, Nebraska, 6, -7:1, 7914, 4647, 1, 0, 0 +22083,Richland Parish, LA, Louisiana, 7, -6:1, 21022, 1148, 1, 0, 0 +08051,Gunnison County, CO, Colorado, 8, -7:1, 12456, 4647, 1, 0, 0 +56033,Sheridan County, WY, Wyoming, 8, -7:1, 25165, 4647, 1, 0, 0 +51530,Buena Vista city, VA, Virginia, 2, -5:1, 6288, 1387, 1, 0, 0 +26119,Montmorency County, MI, Michigan, 4, -5:1, 10011, 1387, 1, 0, 0 +47121,Meigs County, TN, Tennesee, 3, -6:1, 9955, 1148, 1, 0, 0 +17125,Mason County, IL, Illinois, 6, -6:1, 16837, 1148, 1, 0, 0 +28077,Lawrence County, MS, Missippi, 5, -6:1, 13053, 1148, 1, 0, 0 +20107,Linn County, KS, Kansas, 6, -6:1, 9158, 1148, 1, 0, 0 +34029,Ocean County, NJ, New Jersey, 0, -5:1, 489819, 1387, 1, 0, 0 +31083,Harlan County, NE, Nebraska, 6, -6:1, 3748, 1148, 1, 0, 0 +51083,Halifax County, VA, Virginia, 2, -5:1, 36863, 1387, 1, 0, 0 +20071,Greeley County, KS, Kansas, 6, -6:1, 1704, 1148, 1, 0, 0 +46127,Union County, SD, South Dakota, 5, -7:1, 12213, 4647, 1, 0, 0 +19123,Mahaska County, IA, Iowa, 5, -6:1, 21901, 1148, 1, 0, 0 +36119,Westchester County, NY, New York, 1, -5:1, 897920, 1387, 1, 0, 0 +51610,Falls Church city, VA, Virginia, 2, -5:1, 10042, 1387, 1, 0, 0 +28069,Kemper County, MS, Missippi, 5, -6:1, 10575, 1148, 1, 0, 0 +21137,Lincoln County, KY, Kentucky, 4, -5:1, 22367, 1387, 1, 0, 0 +51103,Lancaster County, VA, Virginia, 2, -5:1, 11373, 1387, 1, 0, 0 +13269,Taylor County, GA, Georgia, 3, -5:1, 8306, 1387, 1, 0, 0 +36039,Greene County, NY, New York, 1, -5:1, 47807, 1387, 1, 0, 0 +26123,Newaygo County, MI, Michigan, 4, -5:1, 45784, 1387, 1, 0, 0 +34013,Essex County, NJ, New Jersey, 0, -5:1, 750273, 1387, 1, 0, 0 +17199,Williamson County, IL, Illinois, 6, -6:1, 60819, 1148, 1, 0, 0 +17155,Putnam County, IL, Illinois, 6, -6:1, 5826, 1148, 1, 0, 0 +31177,Washington County, NE, Nebraska, 6, -7:1, 18661, 4647, 1, 0, 0 +37193,Wilkes County, NC, North Carolina, 2, -5:1, 62837, 1387, 1, 0, 0 +37009,Ashe County, NC, North Carolina, 2, -5:1, 24025, 1387, 1, 0, 0 +12121,Suwannee County, FL, Florida, 3, -5:1, 32665, 1387, 1, 0, 0 +29221,Washington County, MO, Mosourri, 6, -6:1, 22966, 1148, 1, 0, 0 +21037,Campbell County, KY, Kentucky, 4, -6:1, 87381, 1148, 1, 0, 0 +53047,Okanogan County, WA, Washiington, 9, -8:1, 38237, 3240, 0, 1, 0 +30073,Pondera County, MT, Montana, 6, -7:1, 6402, 4647, 1, 0, 0 +01121,Talladega County, AL, Alabama, 3, -6:1, 76633, 1148, 1, 0, 0 +13241,Rabun County, GA, Georgia, 3, -5:1, 13406, 1387, 1, 0, 0 +05045,Faulkner County, AR, Arkansas, 7, -6:1, 78382, 1148, 1, 0, 0 +13109,Evans County, GA, Georgia, 3, -5:1, 9949, 1387, 1, 0, 0 +28111,Perry County, MS, Missippi, 5, -6:1, 11798, 1148, 1, 0, 0 +54085,Ritchie County, WV, West Virginia, 2, -5:1, 10356, 1387, 1, 0, 0 +12021,Collier County, FL, Florida, 3, -5:1, 199436, 1387, 1, 0, 0 +19069,Franklin County, IA, Iowa, 5, -6:1, 10863, 1148, 1, 0, 0 +48159,Franklin County, TX, Texas, 7, -6:1, 9676, 1148, 1, 0, 0 +48339,Montgomery County, TX, Texas, 7, -6:1, 271788, 1148, 1, 0, 0 +27119,Polk County, MN, Minnesota, 5, -6:1, 30954, 1148, 1, 0, 0 +26037,Clinton County, MI, Michigan, 4, -5:1, 63379, 1387, 1, 0, 0 +20121,Miami County, KS, Kansas, 6, -6:1, 26597, 1148, 1, 0, 0 +19185,Wayne County, IA, Iowa, 5, -6:1, 6659, 1148, 1, 0, 0 +29051,Cole County, MO, Mosourri, 6, -6:1, 69307, 1148, 1, 0, 0 +36107,Tioga County, NY, New York, 1, -5:1, 52477, 1387, 1, 0, 0 +39145,Scioto County, OH, Ohio, 4, -5:1, 80355, 1387, 1, 0, 0 +12115,Sarasota County, FL, Florida, 3, -5:1, 303400, 1387, 1, 0, 0 +24017,Charles County, MD, Maryland, 2, -5:1, 117963, 1387, 1, 0, 0 +53063,Spokane County, WA, Washiington, 9, -8:1, 408669, 3240, 0, 1, 0 +42009,Bedford County, PA, Pennsylvania, 1, -5:1, 49373, 1387, 1, 0, 0 +17117,Macoupin County, IL, Illinois, 6, -6:1, 48872, 1148, 1, 0, 0 +42037,Columbia County, PA, Pennsylvania, 1, -5:1, 64120, 1387, 1, 0, 0 +46049,Faulk County, SD, South Dakota, 5, -6:1, 2521, 1148, 1, 0, 0 +54097,Upshur County, WV, West Virginia, 2, -5:1, 23526, 1387, 1, 0, 0 +18087,Lagrange County, IN, Indiana, 4, -5:1, 33484, 1387, 1, 0, 0 +04027,Yuma County, AZ, Arizona, 8, -7:1, 132259, 4647, 1, 0, 0 +49029,Morgan County, UT, Utah, 8, -7:1, 7022, 4647, 1, 0, 0 +19013,Black Hawk County, IA, Iowa, 5, -6:1, 121121, 1148, 1, 0, 0 +01037,Coosa County, AL, Alabama, 3, -6:1, 11658, 1148, 1, 0, 0 +09003,Hartford County, CT, Connecticut, 0, -5:1, 828200, 1387, 1, 0, 0 +29175,Randolph County, MO, Mosourri, 6, -6:1, 24024, 1148, 1, 0, 0 +28083,Leflore County, MS, Missippi, 5, -6:1, 36951, 1148, 1, 0, 0 +39049,Franklin County, OH, Ohio, 4, -5:1, 1021194, 1387, 1, 0, 0 +24045,Wicomico County, MD, Maryland, 2, -5:1, 79367, 1387, 1, 0, 0 +39037,Darke County, OH, Ohio, 4, -5:1, 54180, 1387, 1, 0, 0 +21141,Logan County, KY, Kentucky, 4, -5:1, 26145, 1387, 1, 0, 0 +36043,Herkimer County, NY, New York, 1, -5:1, 64049, 1387, 1, 0, 0 +13167,Johnson County, GA, Georgia, 3, -5:1, 8316, 1387, 1, 0, 0 +08037,Eagle County, CO, Colorado, 8, -7:1, 33538, 4647, 1, 0, 0 +18013,Brown County, IN, Indiana, 4, -5:1, 15982, 1387, 1, 0, 0 +13151,Henry County, GA, Georgia, 3, -5:1, 104667, 1387, 1, 0, 0 +39147,Seneca County, OH, Ohio, 4, -5:1, 60099, 1387, 1, 0, 0 +21057,Cumberland County, KY, Kentucky, 4, -6:1, 6828, 1148, 1, 0, 0 +53077,Yakima County, WA, Washiington, 9, -8:1, 218062, 3240, 0, 1, 0 +13003,Atkinson County, GA, Georgia, 3, -5:1, 7138, 1387, 1, 0, 0 +13089,DeKalb County, GA, Georgia, 3, -5:1, 593850, 1387, 1, 0, 0 +37079,Greene County, NC, North Carolina, 2, -5:1, 18308, 1387, 1, 0, 0 +20017,Chase County, KS, Kansas, 6, -6:1, 2950, 1148, 1, 0, 0 +01001,Autauga County, AL, Alabama, 3, -6:1, 42095, 1148, 1, 0, 0 +26065,Ingham County, MI, Michigan, 4, -5:1, 285214, 1387, 1, 0, 0 +48063,Camp County, TX, Texas, 7, -6:1, 10962, 1148, 1, 0, 0 +19093,Ida County, IA, Iowa, 5, -6:1, 7912, 1148, 1, 0, 0 +39173,Wood County, OH, Ohio, 4, -5:1, 119498, 1387, 1, 0, 0 +13227,Pickens County, GA, Georgia, 3, -5:1, 19679, 1387, 1, 0, 0 +26033,Chippewa County, MI, Michigan, 4, -5:1, 37968, 1387, 1, 0, 0 +29023,Butler County, MO, Mosourri, 6, -6:1, 40561, 1148, 1, 0, 0 +47067,Hancock County, TN, Tennesee, 3, -5:1, 6778, 1387, 1, 0, 0 +27091,Martin County, MN, Minnesota, 5, -6:1, 21984, 1148, 1, 0, 0 +19139,Muscatine County, IA, Iowa, 5, -6:1, 41126, 1148, 1, 0, 0 +54011,Cabell County, WV, West Virginia, 2, -5:1, 94273, 1387, 1, 0, 0 +13247,Rockdale County, GA, Georgia, 3, -5:1, 68305, 1387, 1, 0, 0 +12061,Indian River County, FL, Florida, 3, -5:1, 99155, 1387, 1, 0, 0 +31023,Butler County, NE, Nebraska, 6, -6:1, 8680, 1148, 1, 0, 0 +18051,Gibson County, IN, Indiana, 4, -5:1, 32149, 1387, 1, 0, 0 +38073,Ransom County, ND, North Dakota, 5, -6:1, 5776, 1148, 1, 0, 0 +29089,Howard County, MO, Mosourri, 6, -6:1, 9741, 1148, 1, 0, 0 +05023,Cleburne County, AR, Arkansas, 7, -6:1, 22923, 1148, 1, 0, 0 +05109,Pike County, AR, Arkansas, 7, -6:1, 10592, 1148, 1, 0, 0 +51650,Hampton city, VA, Virginia, 2, -5:1, 136968, 1387, 1, 0, 0 +01065,Hale County, AL, Alabama, 3, -6:1, 16744, 1148, 1, 0, 0 +39011,Auglaize County, OH, Ohio, 4, -5:1, 47103, 1387, 1, 0, 0 +51685,Manassas Park city, VA, Virginia, 2, -5:1, 8711, 1387, 1, 0, 0 +27059,Isanti County, MN, Minnesota, 5, -6:1, 30121, 1148, 1, 0, 0 +31037,Colfax County, NE, Nebraska, 6, -6:1, 10716, 1148, 1, 0, 0 +51141,Patrick County, VA, Virginia, 2, -5:1, 18441, 1387, 1, 0, 0 +21153,Magoffin County, KY, Kentucky, 4, -5:1, 13838, 1387, 1, 0, 0 +13139,Hall County, GA, Georgia, 3, -5:1, 119210, 1387, 1, 0, 0 +01091,Marengo County, AL, Alabama, 3, -6:1, 23378, 1148, 1, 0, 0 +27093,Meeker County, MN, Minnesota, 5, -6:1, 21735, 1148, 1, 0, 0 +55087,Outagamie County, WI, Wisconnsin, 5, -6:1, 156269, 1148, 1, 0, 0 +08041,El Paso County, CO, Colorado, 8, -7:1, 490378, 4647, 1, 0, 0 +19129,Mills County, IA, Iowa, 5, -6:1, 14477, 1148, 1, 0, 0 +01063,Greene County, AL, Alabama, 3, -6:1, 9880, 1148, 1, 0, 0 +17047,Edwards County, IL, Illinois, 6, -6:1, 6950, 1148, 1, 0, 0 +18143,Scott County, IN, Indiana, 4, -5:1, 22939, 1387, 1, 0, 0 +08115,Sedgwick County, CO, Colorado, 8, -7:1, 2547, 4647, 1, 0, 0 +37173,Swain County, NC, North Carolina, 2, -5:1, 12300, 1387, 1, 0, 0 +31157,Scotts Bluff County, NE, Nebraska, 6, -7:1, 36109, 4647, 1, 0, 0 +42105,Potter County, PA, Pennsylvania, 1, -5:1, 17184, 1387, 1, 0, 0 +17001,Adams County, IL, Illinois, 6, -6:1, 67105, 1148, 1, 0, 0 +24033,Prince George County, MD, Maryland, 2, -5:1, 777811, 1387, 1, 0, 0 +12095,Orange County, FL, Florida, 3, -5:1, 805837, 1387, 1, 0, 0 +51169,Scott County, VA, Virginia, 2, -5:1, 22605, 1387, 1, 0, 0 +05063,Independence County, AR, Arkansas, 7, -6:1, 33054, 1148, 1, 0, 0 +17141,Ogle County, IL, Illinois, 6, -6:1, 50511, 1148, 1, 0, 0 +27009,Benton County, MN, Minnesota, 5, -6:1, 34128, 1148, 1, 0, 0 +28115,Pontotoc County, MS, Missippi, 5, -6:1, 25397, 1148, 1, 0, 0 +21133,Letcher County, KY, Kentucky, 4, -5:1, 26185, 1387, 1, 0, 0 +51047,Culpeper County, VA, Virginia, 2, -5:1, 33083, 1387, 1, 0, 0 +47019,Carter County, TN, Tennesee, 3, -5:1, 53323, 1387, 1, 0, 0 +08049,Grand County, CO, Colorado, 8, -7:1, 10050, 4647, 1, 0, 0 +17031,Cook County, IL, Illinois, 6, -6:1, 5189689, 1148, 1, 0, 0 +13079,Crawford County, GA, Georgia, 3, -5:1, 10667, 1387, 1, 0, 0 +13217,Newton County, GA, Georgia, 3, -5:1, 57847, 1387, 1, 0, 0 +17201,Winnebago County, IL, Illinois, 6, -6:1, 267642, 1148, 1, 0, 0 +27131,Rice County, MN, Minnesota, 5, -6:1, 54106, 1148, 1, 0, 0 +39137,Putnam County, OH, Ohio, 4, -5:1, 35255, 1387, 1, 0, 0 +19191,Winneshiek County, IA, Iowa, 5, -6:1, 20934, 1148, 1, 0, 0 +20207,Woodson County, KS, Kansas, 6, -6:1, 3983, 1148, 1, 0, 0 +02020,Anchorage Borough, AK, Alaska, 9, -9:1, 254982, 1174, 0, 0, 1 +28065,Jefferson Davis County, MS, Missippi, 5, -6:1, 13860, 1148, 1, 0, 0 +48329,Midland County, TX, Texas, 7, -6:1, 119647, 1148, 1, 0, 0 +19121,Madison County, IA, Iowa, 5, -6:1, 13872, 1148, 1, 0, 0 +08111,San Juan County, CO, Colorado, 8, -7:1, 530, 4647, 1, 0, 0 +37075,Graham County, NC, North Carolina, 2, -5:1, 7647, 1387, 1, 0, 0 +37043,Clay County, NC, North Carolina, 2, -5:1, 8575, 1387, 1, 0, 0 +06011,Colusa County, CA, California, 9, -8:1, 18572, 3240, 0, 1, 0 +37071,Gaston County, NC, North Carolina, 2, -5:1, 184247, 1387, 1, 0, 0 +56041,Uinta County, WY, Wyoming, 8, -7:1, 20465, 4647, 1, 0, 0 +45043,Georgetown County, SC, South Carolina, 2, -5:1, 53727, 1387, 1, 0, 0 +51197,Wythe County, VA, Virginia, 2, -5:1, 26268, 1387, 1, 0, 0 +17027,Clinton County, IL, Illinois, 6, -6:1, 35591, 1148, 1, 0, 0 +45071,Newberry County, SC, South Carolina, 2, -5:1, 34462, 1387, 1, 0, 0 +30013,Cascade County, MT, Montana, 6, -7:1, 78983, 4647, 1, 0, 0 +51163,Rockbridge County, VA, Virginia, 2, -5:1, 19557, 1387, 1, 0, 0 +27135,Roseau County, MN, Minnesota, 5, -6:1, 16120, 1148, 1, 0, 0 +48455,Trinity County, TX, Texas, 7, -6:1, 12613, 1148, 1, 0, 0 +27169,Winona County, MN, Minnesota, 5, -6:1, 48080, 1148, 1, 0, 0 +47065,Hamilton County, TN, Tennesee, 3, -5:1, 294745, 1387, 1, 0, 0 +51027,Buchanan County, VA, Virginia, 2, -5:1, 28929, 1387, 1, 0, 0 +48229,Hudspeth County, TX, Texas, 7, -6:1, 3250, 1148, 1, 0, 0 +26103,Marquette County, MI, Michigan, 4, -5:1, 61565, 1387, 1, 0, 0 +13141,Hancock County, GA, Georgia, 3, -5:1, 9134, 1387, 1, 0, 0 +48187,Guadalupe County, TX, Texas, 7, -6:1, 80472, 1148, 1, 0, 0 +46083,Lincoln County, SD, South Dakota, 5, -7:1, 20411, 4647, 1, 0, 0 +06097,Sonoma County, CA, California, 9, -8:1, 433304, 3240, 0, 1, 0 +12009,Brevard County, FL, Florida, 3, -5:1, 466093, 1387, 1, 0, 0 +42023,Cameron County, PA, Pennsylvania, 1, -5:1, 5620, 1387, 1, 0, 0 +47167,Tipton County, TN, Tennesee, 3, -6:1, 47343, 1148, 1, 0, 0 +48327,Menard County, TX, Texas, 7, -6:1, 2336, 1148, 1, 0, 0 +15009,Maui County, HI, Hawaii, 9, -10:1, 120711, 6750, 0, 0, 1 +55045,Green County, WI, Wisconnsin, 5, -6:1, 33404, 1148, 1, 0, 0 +30053,Lincoln County, MT, Montana, 6, -7:1, 18696, 4647, 1, 0, 0 +20091,Johnson County, KS, Kansas, 6, -6:1, 429563, 1148, 1, 0, 0 +39129,Pickaway County, OH, Ohio, 4, -5:1, 53731, 1387, 1, 0, 0 +19159,Ringgold County, IA, Iowa, 5, -6:1, 5354, 1148, 1, 0, 0 +22067,Morehouse Parish, LA, Louisiana, 7, -6:1, 31477, 1148, 1, 0, 0 +48087,Collingsworth County, TX, Texas, 7, -6:1, 3287, 1148, 1, 0, 0 +39055,Geauga County, OH, Ohio, 4, -5:1, 88788, 1387, 1, 0, 0 +16075,Payette County, ID, Idaho, 8, -7:1, 20519, 4647, 1, 0, 0 +48499,Wood County, TX, Texas, 7, -6:1, 34321, 1148, 1, 0, 0 +31011,Boone County, NE, Nebraska, 6, -6:1, 6377, 1148, 1, 0, 0 +39035,Cuyahoga County, OH, Ohio, 4, -5:1, 1380696, 1387, 1, 0, 0 +22055,Lafayette Parish, LA, Louisiana, 7, -6:1, 186631, 1148, 1, 0, 0 +39141,Ross County, OH, Ohio, 4, -5:1, 75473, 1387, 1, 0, 0 +02261,Valdez-Cordova Census Area, AK, Alaska, 9, -9:1, 10279, 1174, 0, 0, 1 +51730,Petersburg city, VA, Virginia, 2, -5:1, 34724, 1387, 1, 0, 0 +42063,Indiana County, PA, Pennsylvania, 1, -5:1, 88567, 1387, 1, 0, 0 +24029,Kent County, MD, Maryland, 2, -5:1, 18925, 1387, 1, 0, 0 +40107,Okfuskee County, OK, Oklahoma, 7, -6:1, 11402, 1148, 1, 0, 0 +28067,Jones County, MS, Missippi, 5, -6:1, 63461, 1148, 1, 0, 0 +41045,Malheur County, OR, Oregon, 9, -8:1, 28542, 3240, 0, 1, 0 +36081,Queens County, NY, New York, 1, -5:1, 1998853, 1387, 1, 0, 0 +16079,Shoshone County, ID, Idaho, 8, -7:1, 13870, 4647, 1, 0, 0 +08079,Mineral County, CO, Colorado, 8, -7:1, 694, 4647, 1, 0, 0 +21135,Lewis County, KY, Kentucky, 4, -5:1, 13584, 1387, 1, 0, 0 +55007,Bayfield County, WI, Wisconnsin, 5, -6:1, 15151, 1148, 1, 0, 0 +25021,Norfolk County, MA, Massachusetts, 0, -5:1, 642705, 1387, 1, 0, 0 +42081,Lycoming County, PA, Pennsylvania, 1, -5:1, 117308, 1387, 1, 0, 0 +12035,Flagler County, FL, Florida, 3, -5:1, 47455, 1387, 1, 0, 0 +34017,Hudson County, NJ, New Jersey, 0, -5:1, 557159, 1387, 1, 0, 0 +53045,Mason County, WA, Washiington, 9, -8:1, 49867, 3240, 0, 1, 0 +31163,Sherman County, NE, Nebraska, 6, -7:1, 3432, 4647, 1, 0, 0 +46089,McPherson County, SD, South Dakota, 5, -7:1, 2738, 4647, 1, 0, 0 +31007,Banner County, NE, Nebraska, 6, -6:1, 878, 1148, 1, 0, 0 +38029,Emmons County, ND, North Dakota, 5, -6:1, 4311, 1148, 1, 0, 0 +47181,Wayne County, TN, Tennesee, 3, -6:1, 16495, 1148, 1, 0, 0 +51051,Dickenson County, VA, Virginia, 2, -5:1, 16894, 1387, 1, 0, 0 +17143,Peoria County, IL, Illinois, 6, -6:1, 181609, 1148, 1, 0, 0 +50007,Chittenden County, VT, Vermont, 0, -5:1, 142642, 1387, 1, 0, 0 +21047,Christian County, KY, Kentucky, 4, -6:1, 72493, 1148, 1, 0, 0 +31143,Polk County, NE, Nebraska, 6, -7:1, 5631, 4647, 1, 0, 0 +35059,Union County, NM, New Mexico, 8, -7:1, 3985, 4647, 1, 0, 0 +53025,Grant County, WA, Washiington, 9, -8:1, 70545, 3240, 0, 1, 0 +20027,Clay County, KS, Kansas, 6, -6:1, 9148, 1148, 1, 0, 0 +46099,Minnehaha County, SD, South Dakota, 5, -7:1, 143011, 4647, 1, 0, 0 +31171,Thomas County, NE, Nebraska, 6, -7:1, 797, 4647, 1, 0, 0 +20167,Russell County, KS, Kansas, 6, -6:1, 7558, 1148, 1, 0, 0 +08063,Kit Carson County, CO, Colorado, 8, -7:1, 7313, 4647, 1, 0, 0 +37033,Caswell County, NC, North Carolina, 2, -5:1, 22215, 1387, 1, 0, 0 +55101,Racine County, WI, Wisconnsin, 5, -6:1, 186119, 1148, 1, 0, 0 +48463,Uvalde County, TX, Texas, 7, -6:1, 25565, 1148, 1, 0, 0 +22057,Lafourche Parish, LA, Louisiana, 7, -6:1, 89324, 1148, 1, 0, 0 +20201,Washington County, KS, Kansas, 6, -6:1, 6490, 1148, 1, 0, 0 +13253,Seminole County, GA, Georgia, 3, -5:1, 9788, 1387, 1, 0, 0 +22001,Acadia Parish, LA, Louisiana, 7, -6:1, 57721, 1148, 1, 0, 0 +39009,Athens County, OH, Ohio, 4, -5:1, 61490, 1387, 1, 0, 0 +21093,Hardin County, KY, Kentucky, 4, -6:1, 91462, 1148, 1, 0, 0 +42083,McKean County, PA, Pennsylvania, 1, -5:1, 46500, 1387, 1, 0, 0 +13169,Jones County, GA, Georgia, 3, -5:1, 23020, 1387, 1, 0, 0 +39099,Mahoning County, OH, Ohio, 4, -5:1, 255165, 1387, 1, 0, 0 +48093,Comanche County, TX, Texas, 7, -6:1, 13568, 1148, 1, 0, 0 +47057,Grainger County, TN, Tennesee, 3, -5:1, 19829, 1387, 1, 0, 0 +15003,Honolulu County, HI, Hawaii, 9, -10:1, 872478, 6750, 0, 0, 1 +13001,Appling County, GA, Georgia, 3, -5:1, 16493, 1387, 1, 0, 0 +30091,Sheridan County, MT, Montana, 6, -7:1, 4269, 4647, 1, 0, 0 +05143,Washington County, AR, Arkansas, 7, -6:1, 138454, 1148, 1, 0, 0 +08045,Garfield County, CO, Colorado, 8, -7:1, 39301, 4647, 1, 0, 0 +13157,Jackson County, GA, Georgia, 3, -5:1, 37641, 1387, 1, 0, 0 +29195,Saline County, MO, Mosourri, 6, -6:1, 22703, 1148, 1, 0, 0 +16067,Minidoka County, ID, Idaho, 8, -7:1, 20207, 4647, 1, 0, 0 +48199,Hardin County, TX, Texas, 7, -6:1, 48758, 1148, 1, 0, 0 +16021,Boundary County, ID, Idaho, 8, -7:1, 9800, 4647, 1, 0, 0 +21123,Larue County, KY, Kentucky, 4, -5:1, 13058, 1387, 1, 0, 0 +23031,York County, ME, Maine, 0, -5:1, 175165, 1387, 1, 0, 0 +27133,Rock County, MN, Minnesota, 5, -6:1, 9743, 1148, 1, 0, 0 +17119,Madison County, IL, Illinois, 6, -6:1, 259351, 1148, 1, 0, 0 +28123,Scott County, MS, Missippi, 5, -6:1, 25001, 1148, 1, 0, 0 +37143,Perquimans County, NC, North Carolina, 2, -5:1, 11282, 1387, 1, 0, 0 +13145,Harris County, GA, Georgia, 3, -5:1, 22315, 1387, 1, 0, 0 +49023,Juab County, UT, Utah, 8, -7:1, 7572, 4647, 1, 0, 0 +38087,Slope County, ND, North Dakota, 5, -6:1, 865, 1148, 1, 0, 0 +13175,Laurens County, GA, Georgia, 3, -5:1, 43772, 1387, 1, 0, 0 +49025,Kane County, UT, Utah, 8, -7:1, 6200, 4647, 1, 0, 0 +36055,Monroe County, NY, New York, 1, -5:1, 716072, 1387, 1, 0, 0 +26045,Eaton County, MI, Michigan, 4, -5:1, 101090, 1387, 1, 0, 0 +05097,Montgomery County, AR, Arkansas, 7, -6:1, 8655, 1148, 1, 0, 0 +12015,Charlotte County, FL, Florida, 3, -5:1, 134899, 1387, 1, 0, 0 +13049,Charlton County, GA, Georgia, 3, -5:1, 9442, 1387, 1, 0, 0 +20159,Rice County, KS, Kansas, 6, -6:1, 10360, 1148, 1, 0, 0 +48277,Lamar County, TX, Texas, 7, -6:1, 46045, 1148, 1, 0, 0 +29029,Camden County, MO, Mosourri, 6, -6:1, 33952, 1148, 1, 0, 0 +12067,Lafayette County, FL, Florida, 3, -5:1, 6325, 1387, 1, 0, 0 +29223,Wayne County, MO, Mosourri, 6, -6:1, 13059, 1148, 1, 0, 0 +48433,Stonewall County, TX, Texas, 7, -6:1, 1783, 1148, 1, 0, 0 +20099,Labette County, KS, Kansas, 6, -6:1, 23030, 1148, 1, 0, 0 +51199,York County, VA, Virginia, 2, -5:1, 58789, 1387, 1, 0, 0 +39149,Shelby County, OH, Ohio, 4, -5:1, 47457, 1387, 1, 0, 0 +22113,Vermilion Parish, LA, Louisiana, 7, -6:1, 52090, 1148, 1, 0, 0 +39093,Lorain County, OH, Ohio, 4, -5:1, 282149, 1387, 1, 0, 0 +53003,Asotin County, WA, Washiington, 9, -8:1, 21264, 3240, 0, 1, 0 +18149,Starke County, IN, Indiana, 4, -5:1, 23968, 1387, 1, 0, 0 +26041,Delta County, MI, Michigan, 4, -5:1, 38947, 1387, 1, 0, 0 +30005,Blaine County, MT, Montana, 6, -7:1, 7148, 4647, 1, 0, 0 +05027,Columbia County, AR, Arkansas, 7, -6:1, 25060, 1148, 1, 0, 0 +08011,Bent County, CO, Colorado, 8, -7:1, 5497, 4647, 1, 0, 0 +51119,Middlesex County, VA, Virginia, 2, -5:1, 9630, 1387, 1, 0, 0 +48471,Walker County, TX, Texas, 7, -6:1, 54972, 1148, 1, 0, 0 +12063,Jackson County, FL, Florida, 3, -5:1, 45660, 1387, 1, 0, 0 +31101,Keith County, NE, Nebraska, 6, -7:1, 8665, 4647, 1, 0, 0 +21035,Calloway County, KY, Kentucky, 4, -6:1, 33478, 1148, 1, 0, 0 +28125,Sharkey County, MS, Missippi, 5, -6:1, 6650, 1148, 1, 0, 0 +05127,Scott County, AR, Arkansas, 7, -6:1, 10686, 1148, 1, 0, 0 +08093,Park County, CO, Colorado, 8, -7:1, 13399, 4647, 1, 0, 0 +27115,Pine County, MN, Minnesota, 5, -6:1, 23916, 1148, 1, 0, 0 +53069,Wahkiakum County, WA, Washiington, 9, -8:1, 3857, 3240, 0, 1, 0 +02180,Nome Census Area, AK, Alaska, 9, -9:1, 9016, 1174, 0, 0, 1 +21205,Rowan County, KY, Kentucky, 4, -5:1, 22196, 1387, 1, 0, 0 +37057,Davidson County, NC, North Carolina, 2, -5:1, 141178, 1387, 1, 0, 0 +37013,Beaufort County, NC, North Carolina, 2, -5:1, 44531, 1387, 1, 0, 0 +50005,Caledonia County, VT, Vermont, 0, -5:1, 28529, 1387, 1, 0, 0 +26043,Dickinson County, MI, Michigan, 4, -5:1, 27074, 1387, 1, 0, 0 +06015,Del Norte County, CA, California, 9, -8:1, 27000, 3240, 0, 1, 0 +55107,Rusk County, WI, Wisconnsin, 5, -6:1, 15252, 1148, 1, 0, 0 +22061,Lincoln Parish, LA, Louisiana, 7, -6:1, 41560, 1148, 1, 0, 0 +48117,Deaf Smith County, TX, Texas, 7, -6:1, 19061, 1148, 1, 0, 0 +27079,Le Sueur County, MN, Minnesota, 5, -6:1, 25320, 1148, 1, 0, 0 +39033,Crawford County, OH, Ohio, 4, -5:1, 47217, 1387, 1, 0, 0 +37003,Alexander County, NC, North Carolina, 2, -5:1, 31280, 1387, 1, 0, 0 +21011,Bath County, KY, Kentucky, 4, -6:1, 10570, 1148, 1, 0, 0 +08005,Arapahoe County, CO, Colorado, 8, -7:1, 473168, 4647, 1, 0, 0 +51775,Salem city, VA, Virginia, 2, -5:1, 24679, 1387, 1, 0, 0 +36061,New York County, NY, New York, 1, -5:1, 1550649, 1387, 1, 0, 0 +26001,Alcona County, MI, Michigan, 4, -5:1, 11108, 1387, 1, 0, 0 +12041,Gilchrist County, FL, Florida, 3, -5:1, 13791, 1387, 1, 0, 0 +33015,Rockingham County, NH, New Hampshire, 0, -5:1, 271152, 1387, 1, 0, 0 +15007,Kauai County, HI, Hawaii, 9, -10:1, 56603, 6750, 0, 0, 1 +47163,Sullivan County, TN, Tennesee, 3, -6:1, 150617, 1148, 1, 0, 0 +51191,Washington County, VA, Virginia, 2, -5:1, 49168, 1387, 1, 0, 0 +55117,Sheboygan County, WI, Wisconnsin, 5, -6:1, 110170, 1148, 1, 0, 0 +12051,Hendry County, FL, Florida, 3, -5:1, 29357, 1387, 1, 0, 0 +51510,Alexandria city, VA, Virginia, 2, -5:1, 118300, 1387, 1, 0, 0 +21045,Casey County, KY, Kentucky, 4, -6:1, 14773, 1148, 1, 0, 0 +31125,Nance County, NE, Nebraska, 6, -7:1, 4099, 4647, 1, 0, 0 +27155,Traverse County, MN, Minnesota, 5, -6:1, 4248, 1148, 1, 0, 0 +34005,Burlington County, NJ, New Jersey, 0, -5:1, 420323, 1387, 1, 0, 0 +12005,Bay County, FL, Florida, 3, -5:1, 146999, 1387, 1, 0, 0 +47017,Carroll County, TN, Tennesee, 3, -5:1, 29115, 1387, 1, 0, 0 +19035,Cherokee County, IA, Iowa, 5, -6:1, 13191, 1148, 1, 0, 0 +13243,Randolph County, GA, Georgia, 3, -5:1, 7881, 1387, 1, 0, 0 +05135,Sharp County, AR, Arkansas, 7, -6:1, 16993, 1148, 1, 0, 0 +27017,Carlton County, MN, Minnesota, 5, -6:1, 30817, 1148, 1, 0, 0 +48435,Sutton County, TX, Texas, 7, -6:1, 4463, 1148, 1, 0, 0 +33009,Grafton County, NH, New Hampshire, 0, -5:1, 78277, 1387, 1, 0, 0 +39017,Butler County, OH, Ohio, 4, -5:1, 330428, 1387, 1, 0, 0 +36021,Columbia County, NY, New York, 1, -5:1, 63221, 1387, 1, 0, 0 +41061,Union County, OR, Oregon, 9, -8:1, 24829, 3240, 0, 1, 0 +13059,Clarke County, GA, Georgia, 3, -5:1, 90630, 1387, 1, 0, 0 +48243,Jeff Davis County, TX, Texas, 7, -6:1, 2356, 1148, 1, 0, 0 +55055,Jefferson County, WI, Wisconnsin, 5, -6:1, 73550, 1148, 1, 0, 0 +36003,Allegany County, NY, New York, 1, -5:1, 50997, 1387, 1, 0, 0 +51159,Richmond County, VA, Virginia, 2, -5:1, 8665, 1387, 1, 0, 0 +38043,Kidder County, ND, North Dakota, 5, -6:1, 2877, 1148, 1, 0, 0 +48215,Hidalgo County, TX, Texas, 7, -6:1, 522204, 1148, 1, 0, 0 +27073,Lac qui Parle County, MN, Minnesota, 5, -6:1, 8022, 1148, 1, 0, 0 +55015,Calumet County, WI, Wisconnsin, 5, -6:1, 38377, 1148, 1, 0, 0 +48119,Delta County, TX, Texas, 7, -6:1, 4945, 1148, 1, 0, 0 +13095,Dougherty County, GA, Georgia, 3, -5:1, 95309, 1387, 1, 0, 0 +19165,Shelby County, IA, Iowa, 5, -6:1, 12978, 1148, 1, 0, 0 +53017,Douglas County, WA, Washiington, 9, -8:1, 33631, 3240, 0, 1, 0 +19089,Howard County, IA, Iowa, 5, -6:1, 9689, 1148, 1, 0, 0 +01073,Jefferson County, AL, Alabama, 3, -6:1, 659524, 1148, 1, 0, 0 +48047,Brooks County, TX, Texas, 7, -6:1, 8501, 1148, 1, 0, 0 +13179,Liberty County, GA, Georgia, 3, -5:1, 59162, 1387, 1, 0, 0 +20055,Finney County, KS, Kansas, 6, -6:1, 36514, 1148, 1, 0, 0 +27161,Waseca County, MN, Minnesota, 5, -6:1, 18178, 1148, 1, 0, 0 +19187,Webster County, IA, Iowa, 5, -6:1, 38705, 1148, 1, 0, 0 +28005,Amite County, MS, Missippi, 5, -6:1, 13752, 1148, 1, 0, 0 +35015,Eddy County, NM, New Mexico, 8, -7:1, 53543, 4647, 1, 0, 0 +48415,Scurry County, TX, Texas, 7, -6:1, 18073, 1148, 1, 0, 0 +20035,Cowley County, KS, Kansas, 6, -6:1, 36319, 1148, 1, 0, 0 +51093,Isle of Wight County, VA, Virginia, 2, -5:1, 29252, 1387, 1, 0, 0 +29143,New Madrid County, MO, Mosourri, 6, -6:1, 20370, 1148, 1, 0, 0 +48013,Atascosa County, TX, Texas, 7, -6:1, 36471, 1148, 1, 0, 0 +01069,Houston County, AL, Alabama, 3, -6:1, 85877, 1148, 1, 0, 0 +19151,Pocahontas County, IA, Iowa, 5, -6:1, 8777, 1148, 1, 0, 0 +46027,Clay County, SD, South Dakota, 5, -6:1, 15167, 1148, 1, 0, 0 +06107,Tulare County, CA, California, 9, -8:1, 355240, 3240, 0, 1, 0 +01079,Lawrence County, AL, Alabama, 3, -6:1, 33447, 1148, 1, 0, 0 +48377,Presidio County, TX, Texas, 7, -6:1, 8636, 1148, 1, 0, 0 +40115,Ottawa County, OK, Oklahoma, 7, -6:1, 30944, 1148, 1, 0, 0 +18091,La Porte County, IN, Indiana, 4, -5:1, 109461, 1387, 1, 0, 0 +12057,Hillsborough County, FL, Florida, 3, -5:1, 925277, 1387, 1, 0, 0 +38085,Sioux County, ND, North Dakota, 5, -6:1, 4192, 1148, 1, 0, 0 +42133,York County, PA, Pennsylvania, 1, -5:1, 373255, 1387, 1, 0, 0 +08059,Jefferson County, CO, Colorado, 8, -7:1, 501591, 4647, 1, 0, 0 +13209,Montgomery County, GA, Georgia, 3, -5:1, 7741, 1387, 1, 0, 0 +35041,Roosevelt County, NM, New Mexico, 8, -7:1, 18185, 4647, 1, 0, 0 +55039,Fond du Lac County, WI, Wisconnsin, 5, -6:1, 94690, 1148, 1, 0, 0 +13309,Wheeler County, GA, Georgia, 3, -5:1, 4875, 1387, 1, 0, 0 +13299,Ware County, GA, Georgia, 3, -5:1, 35364, 1387, 1, 0, 0 +27069,Kittson County, MN, Minnesota, 5, -6:1, 5322, 1148, 1, 0, 0 +06055,Napa County, CA, California, 9, -8:1, 119288, 3240, 0, 1, 0 +54035,Jackson County, WV, West Virginia, 2, -5:1, 27972, 1387, 1, 0, 0 +29033,Carroll County, MO, Mosourri, 6, -6:1, 10217, 1148, 1, 0, 0 +35017,Grant County, NM, New Mexico, 8, -7:1, 31612, 4647, 1, 0, 0 +48021,Bastrop County, TX, Texas, 7, -6:1, 50390, 1148, 1, 0, 0 +17003,Alexander County, IL, Illinois, 6, -6:1, 9745, 1148, 1, 0, 0 +21071,Floyd County, KY, Kentucky, 4, -6:1, 43340, 1148, 1, 0, 0 +20137,Norton County, KS, Kansas, 6, -6:1, 5752, 1148, 1, 0, 0 +48447,Throckmorton County, TX, Texas, 7, -6:1, 1727, 1148, 1, 0, 0 +16007,Bear Lake County, ID, Idaho, 8, -7:1, 6539, 4647, 1, 0, 0 +39079,Jackson County, OH, Ohio, 4, -5:1, 32563, 1387, 1, 0, 0 +06093,Siskiyou County, CA, California, 9, -8:1, 44044, 3240, 0, 1, 0 +36059,Nassau County, NY, New York, 1, -5:1, 1302220, 1387, 1, 0, 0 +36097,Schuyler County, NY, New York, 1, -5:1, 19125, 1387, 1, 0, 0 +31057,Dundy County, NE, Nebraska, 6, -6:1, 2302, 1148, 1, 0, 0 +37147,Pitt County, NC, North Carolina, 2, -5:1, 126630, 1387, 1, 0, 0 +51520,Bristol city, VA, Virginia, 2, -5:1, 17486, 1387, 1, 0, 0 +54087,Roane County, WV, West Virginia, 2, -5:1, 15342, 1387, 1, 0, 0 +54103,Wetzel County, WV, West Virginia, 2, -5:1, 18256, 1387, 1, 0, 0 +05147,Woodruff County, AR, Arkansas, 7, -6:1, 8888, 1148, 1, 0, 0 +29119,McDonald County, MO, Mosourri, 6, -6:1, 19887, 1148, 1, 0, 0 +40135,Sequoyah County, OK, Oklahoma, 7, -6:1, 37531, 1148, 1, 0, 0 +13245,Richmond County, GA, Georgia, 3, -5:1, 191329, 1387, 1, 0, 0 +26027,Cass County, MI, Michigan, 4, -5:1, 49693, 1387, 1, 0, 0 +46077,Kingsbury County, SD, South Dakota, 5, -7:1, 5712, 4647, 1, 0, 0 +30001,Beaverhead County, MT, Montana, 6, -7:1, 8867, 4647, 1, 0, 0 +06041,Marin County, CA, California, 9, -8:1, 236770, 3240, 0, 1, 0 +48219,Hockley County, TX, Texas, 7, -6:1, 23788, 1148, 1, 0, 0 +34001,Atlantic County, NJ, New Jersey, 0, -5:1, 238047, 1387, 1, 0, 0 +48029,Bexar County, TX, Texas, 7, -6:1, 1353052, 1148, 1, 0, 0 +46101,Moody County, SD, South Dakota, 5, -7:1, 6505, 4647, 1, 0, 0 +20153,Rawlins County, KS, Kansas, 6, -6:1, 3125, 1148, 1, 0, 0 +05005,Baxter County, AR, Arkansas, 7, -6:1, 36402, 1148, 1, 0, 0 +32021,Mineral County, NV, Nevada, 8, -8:1, 5463, 3240, 0, 1, 0 +13275,Thomas County, GA, Georgia, 3, -5:1, 42953, 1387, 1, 0, 0 +20123,Mitchell County, KS, Kansas, 6, -6:1, 6936, 1148, 1, 0, 0 +29097,Jasper County, MO, Mosourri, 6, -6:1, 99532, 1148, 1, 0, 0 +45081,Saluda County, SC, South Carolina, 2, -5:1, 17025, 1387, 1, 0, 0 +17101,Lawrence County, IL, Illinois, 6, -6:1, 15343, 1148, 1, 0, 0 +26087,Lapeer County, MI, Michigan, 4, -5:1, 88270, 1387, 1, 0, 0 +32017,Lincoln County, NV, Nevada, 8, -8:1, 4220, 3240, 0, 1, 0 +46003,Aurora County, SD, South Dakota, 5, -6:1, 2975, 1148, 1, 0, 0 +21055,Crittenden County, KY, Kentucky, 4, -6:1, 9574, 1148, 1, 0, 0 +13229,Pierce County, GA, Georgia, 3, -5:1, 15794, 1387, 1, 0, 0 +48115,Dawson County, TX, Texas, 7, -6:1, 14700, 1148, 1, 0, 0 +37085,Harnett County, NC, North Carolina, 2, -5:1, 82391, 1387, 1, 0, 0 +39167,Washington County, OH, Ohio, 4, -5:1, 63413, 1387, 1, 0, 0 +47189,Wilson County, TN, Tennesee, 3, -5:1, 83923, 1387, 1, 0, 0 +05053,Grant County, AR, Arkansas, 7, -6:1, 15897, 1148, 1, 0, 0 +46067,Hutchinson County, SD, South Dakota, 5, -7:1, 8045, 4647, 1, 0, 0 +48351,Newton County, TX, Texas, 7, -6:1, 14243, 1148, 1, 0, 0 +48045,Briscoe County, TX, Texas, 7, -6:1, 1888, 1148, 1, 0, 0 +37019,Brunswick County, NC, North Carolina, 2, -5:1, 68416, 1387, 1, 0, 0 +13313,Whitfield County, GA, Georgia, 3, -5:1, 82039, 1387, 1, 0, 0 +54003,Berkeley County, WV, West Virginia, 2, -5:1, 70970, 1387, 1, 0, 0 +13291,Union County, GA, Georgia, 3, -5:1, 16519, 1387, 1, 0, 0 +36047,Kings County, NY, New York, 1, -5:1, 2267942, 1387, 1, 0, 0 +42127,Wayne County, PA, Pennsylvania, 1, -5:1, 45226, 1387, 1, 0, 0 +31145,Red Willow County, NE, Nebraska, 6, -7:1, 11255, 4647, 1, 0, 0 +28061,Jasper County, MS, Missippi, 5, -6:1, 17672, 1148, 1, 0, 0 +08119,Teller County, CO, Colorado, 8, -7:1, 20606, 4647, 1, 0, 0 +19085,Harrison County, IA, Iowa, 5, -6:1, 15364, 1148, 1, 0, 0 +19137,Montgomery County, IA, Iowa, 5, -6:1, 11910, 1148, 1, 0, 0 +41005,Clackamas County, OR, Oregon, 9, -8:1, 334732, 3240, 0, 1, 0 +42011,Berks County, PA, Pennsylvania, 1, -5:1, 355956, 1387, 1, 0, 0 +26009,Antrim County, MI, Michigan, 4, -5:1, 21522, 1387, 1, 0, 0 +08013,Boulder County, CO, Colorado, 8, -7:1, 267274, 4647, 1, 0, 0 +26151,Sanilac County, MI, Michigan, 4, -5:1, 42975, 1387, 1, 0, 0 +30031,Gallatin County, MT, Montana, 6, -7:1, 62545, 4647, 1, 0, 0 +48009,Archer County, TX, Texas, 7, -6:1, 8333, 1148, 1, 0, 0 +08065,Lake County, CO, Colorado, 8, -7:1, 6391, 4647, 1, 0, 0 +47031,Coffee County, TN, Tennesee, 3, -5:1, 45767, 1387, 1, 0, 0 +30087,Rosebud County, MT, Montana, 6, -7:1, 10050, 4647, 1, 0, 0 +40053,Grant County, OK, Oklahoma, 7, -6:1, 5338, 1148, 1, 0, 0 +38083,Sheridan County, ND, North Dakota, 5, -6:1, 1694, 1148, 1, 0, 0 +21095,Harlan County, KY, Kentucky, 4, -6:1, 34950, 1148, 1, 0, 0 +20029,Cloud County, KS, Kansas, 6, -6:1, 10027, 1148, 1, 0, 0 +55035,Eau Claire County, WI, Wisconnsin, 5, -6:1, 89287, 1148, 1, 0, 0 +21111,Jefferson County, KY, Kentucky, 4, -6:1, 672104, 1148, 1, 0, 0 +36121,Wyoming County, NY, New York, 1, -5:1, 44049, 1387, 1, 0, 0 +24047,Worcester County, MD, Maryland, 2, -5:1, 42789, 1387, 1, 0, 0 +20209,Wyandotte County, KS, Kansas, 6, -6:1, 152355, 1148, 1, 0, 0 +30103,Treasure County, MT, Montana, 6, -7:1, 870, 4647, 1, 0, 0 +01019,Cherokee County, AL, Alabama, 3, -6:1, 21833, 1148, 1, 0, 0 +20197,Wabaunsee County, KS, Kansas, 6, -6:1, 6651, 1148, 1, 0, 0 +56045,Weston County, WY, Wyoming, 8, -7:1, 6472, 4647, 1, 0, 0 +47173,Union County, TN, Tennesee, 3, -6:1, 16260, 1148, 1, 0, 0 +12101,Pasco County, FL, Florida, 3, -5:1, 325824, 1387, 1, 0, 0 +28081,Lee County, MS, Missippi, 5, -6:1, 74637, 1148, 1, 0, 0 +29047,Clay County, MO, Mosourri, 6, -6:1, 176206, 1148, 1, 0, 0 +30111,Yellowstone County, MT, Montana, 6, -7:1, 126158, 4647, 1, 0, 0 +47139,Polk County, TN, Tennesee, 3, -6:1, 14883, 1148, 1, 0, 0 +37109,Lincoln County, NC, North Carolina, 2, -5:1, 58093, 1387, 1, 0, 0 +27129,Renville County, MN, Minnesota, 5, -6:1, 16923, 1148, 1, 0, 0 +54005,Boone County, WV, West Virginia, 2, -5:1, 26118, 1387, 1, 0, 0 +42033,Clearfield County, PA, Pennsylvania, 1, -5:1, 80752, 1387, 1, 0, 0 +32013,Humboldt County, NV, Nevada, 8, -8:1, 18145, 3240, 0, 1, 0 +35023,Hidalgo County, NM, New Mexico, 8, -7:1, 6210, 4647, 1, 0, 0 +39157,Tuscarawas County, OH, Ohio, 4, -5:1, 88608, 1387, 1, 0, 0 +20173,Sedgwick County, KS, Kansas, 6, -6:1, 448050, 1148, 1, 0, 0 +13295,Walker County, GA, Georgia, 3, -5:1, 63082, 1387, 1, 0, 0 +49043,Summit County, UT, Utah, 8, -7:1, 26746, 4647, 1, 0, 0 +53065,Stevens County, WA, Washiington, 9, -8:1, 39464, 3240, 0, 1, 0 +49007,Carbon County, UT, Utah, 8, -7:1, 20966, 4647, 1, 0, 0 +16063,Lincoln County, ID, Idaho, 8, -7:1, 3792, 4647, 1, 0, 0 +20037,Crawford County, KS, Kansas, 6, -6:1, 36360, 1148, 1, 0, 0 +37025,Cabarrus County, NC, North Carolina, 2, -5:1, 120057, 1387, 1, 0, 0 +38069,Pierce County, ND, North Dakota, 5, -6:1, 4623, 1148, 1, 0, 0 +27097,Morrison County, MN, Minnesota, 5, -6:1, 30543, 1148, 1, 0, 0 +51840,Winchester city, VA, Virginia, 2, -5:1, 22659, 1387, 1, 0, 0 +54071,Pendleton County, WV, West Virginia, 2, -5:1, 8062, 1387, 1, 0, 0 +05033,Crawford County, AR, Arkansas, 7, -6:1, 50334, 1148, 1, 0, 0 +39073,Hocking County, OH, Ohio, 4, -5:1, 29004, 1387, 1, 0, 0 +08031,Denver County, CO, Colorado, 8, -7:1, 499055, 4647, 1, 0, 0 +48247,Jim Hogg County, TX, Texas, 7, -6:1, 5007, 1148, 1, 0, 0 +13143,Haralson County, GA, Georgia, 3, -5:1, 24653, 1387, 1, 0, 0 +26015,Barry County, MI, Michigan, 4, -5:1, 54535, 1387, 1, 0, 0 +06113,Yolo County, CA, California, 9, -8:1, 153849, 3240, 0, 1, 0 +19039,Clarke County, IA, Iowa, 5, -6:1, 8362, 1148, 1, 0, 0 +17123,Marshall County, IL, Illinois, 6, -6:1, 12882, 1148, 1, 0, 0 +29095,Jackson County, MO, Mosourri, 6, -6:1, 654986, 1148, 1, 0, 0 +48227,Howard County, TX, Texas, 7, -6:1, 32051, 1148, 1, 0, 0 +01103,Morgan County, AL, Alabama, 3, -6:1, 109369, 1148, 1, 0, 0 +02050,Bethel Census Area, AK, Alaska, 9, -9:1, 15967, 1174, 0, 0, 1 +55073,Marathon County, WI, Wisconnsin, 5, -6:1, 123223, 1148, 1, 0, 0 +51560,Clifton Forge city, VA, Virginia, 2, -5:1, 4342, 1387, 1, 0, 0 +13047,Catoosa County, GA, Georgia, 3, -5:1, 50547, 1387, 1, 0, 0 +50023,Washington County, VT, Vermont, 0, -5:1, 56308, 1387, 1, 0, 0 +56037,Sweetwater County, WY, Wyoming, 8, -7:1, 39780, 4647, 1, 0, 0 +21175,Morgan County, KY, Kentucky, 4, -5:1, 13559, 1387, 1, 0, 0 +48505,Zapata County, TX, Texas, 7, -6:1, 11491, 1148, 1, 0, 0 +04005,Coconino County, AZ, Arizona, 8, -7:1, 114171, 4647, 1, 0, 0 +36037,Genesee County, NY, New York, 1, -5:1, 60654, 1387, 1, 0, 0 +26007,Alpena County, MI, Michigan, 4, -5:1, 30405, 1387, 1, 0, 0 +17063,Grundy County, IL, Illinois, 6, -6:1, 36686, 1148, 1, 0, 0 +18049,Fulton County, IN, Indiana, 4, -5:1, 20620, 1387, 1, 0, 0 +13205,Mitchell County, GA, Georgia, 3, -5:1, 21176, 1387, 1, 0, 0 +48273,Kleberg County, TX, Texas, 7, -6:1, 30163, 1148, 1, 0, 0 +38077,Richland County, ND, North Dakota, 5, -6:1, 18272, 1148, 1, 0, 0 +42029,Chester County, PA, Pennsylvania, 1, -5:1, 421686, 1387, 1, 0, 0 +13231,Pike County, GA, Georgia, 3, -5:1, 12645, 1387, 1, 0, 0 +53071,Walla Walla County, WA, Washiington, 9, -8:1, 53702, 3240, 0, 1, 0 +21061,Edmonson County, KY, Kentucky, 4, -6:1, 11353, 1148, 1, 0, 0 +34009,Cape May County, NJ, New Jersey, 0, -5:1, 98069, 1387, 1, 0, 0 +40027,Cleveland County, OK, Oklahoma, 7, -6:1, 201110, 1148, 1, 0, 0 +47027,Clay County, TN, Tennesee, 3, -5:1, 7255, 1387, 1, 0, 0 +06049,Modoc County, CA, California, 9, -8:1, 9398, 3240, 0, 1, 0 +45023,Chester County, SC, South Carolina, 2, -5:1, 34401, 1387, 1, 0, 0 +42113,Sullivan County, PA, Pennsylvania, 1, -5:1, 6107, 1387, 1, 0, 0 +54105,Wirt County, WV, West Virginia, 2, -5:1, 5669, 1387, 1, 0, 0 +06007,Butte County, CA, California, 9, -8:1, 194597, 3240, 0, 1, 0 +16037,Custer County, ID, Idaho, 8, -7:1, 4107, 4647, 1, 0, 0 +13219,Oconee County, GA, Georgia, 3, -5:1, 23737, 1387, 1, 0, 0 +54057,Mineral County, WV, West Virginia, 2, -5:1, 26737, 1387, 1, 0, 0 +26049,Genesee County, MI, Michigan, 4, -5:1, 436084, 1387, 1, 0, 0 +13127,Glynn County, GA, Georgia, 3, -5:1, 67320, 1387, 1, 0, 0 +29025,Caldwell County, MO, Mosourri, 6, -6:1, 8838, 1148, 1, 0, 0 +13105,Elbert County, GA, Georgia, 3, -5:1, 19335, 1387, 1, 0, 0 +42121,Venango County, PA, Pennsylvania, 1, -5:1, 57844, 1387, 1, 0, 0 +28003,Alcorn County, MS, Missippi, 5, -6:1, 32716, 1148, 1, 0, 0 +55051,Iron County, WI, Wisconnsin, 5, -6:1, 6350, 1148, 1, 0, 0 +51115,Mathews County, VA, Virginia, 2, -5:1, 9073, 1387, 1, 0, 0 +27065,Kanabec County, MN, Minnesota, 5, -6:1, 14173, 1148, 1, 0, 0 +20149,Pottawatomie County, KS, Kansas, 6, -6:1, 18691, 1148, 1, 0, 0 +25019,Nantucket County, MA, Massachusetts, 0, -5:1, 7844, 1387, 1, 0, 0 +19117,Lucas County, IA, Iowa, 5, -6:1, 9152, 1148, 1, 0, 0 +38015,Burleigh County, ND, North Dakota, 5, -6:1, 66867, 1148, 1, 0, 0 +41041,Lincoln County, OR, Oregon, 9, -8:1, 45368, 3240, 0, 1, 0 +50013,Grand Isle County, VT, Vermont, 0, -5:1, 6236, 1387, 1, 0, 0 +47055,Giles County, TN, Tennesee, 3, -5:1, 28925, 1387, 1, 0, 0 +01007,Bibb County, AL, Alabama, 3, -6:1, 18926, 1148, 1, 0, 0 +51143,Pittsylvania County, VA, Virginia, 2, -5:1, 57384, 1387, 1, 0, 0 +51061,Fauquier County, VA, Virginia, 2, -5:1, 54109, 1387, 1, 0, 0 +48069,Castro County, TX, Texas, 7, -6:1, 8357, 1148, 1, 0, 0 +39071,Highland County, OH, Ohio, 4, -5:1, 40364, 1387, 1, 0, 0 +02185,North Slope Borough, AK, Alaska, 9, -9:1, 7152, 1174, 0, 0, 1 +56015,Goshen County, WY, Wyoming, 8, -7:1, 12886, 4647, 1, 0, 0 +17165,Saline County, IL, Illinois, 6, -6:1, 26149, 1148, 1, 0, 0 +27167,Wilkin County, MN, Minnesota, 5, -6:1, 7312, 1148, 1, 0, 0 +28009,Benton County, MS, Missippi, 5, -6:1, 8140, 1148, 1, 0, 0 +28013,Calhoun County, MS, Missippi, 5, -6:1, 14822, 1148, 1, 0, 0 +12107,Putnam County, FL, Florida, 3, -5:1, 70419, 1387, 1, 0, 0 +31041,Custer County, NE, Nebraska, 6, -6:1, 12026, 1148, 1, 0, 0 +17099,La Salle County, IL, Illinois, 6, -6:1, 110189, 1148, 1, 0, 0 +47175,Van Buren County, TN, Tennesee, 3, -6:1, 5071, 1148, 1, 0, 0 +13065,Clinch County, GA, Georgia, 3, -5:1, 6660, 1387, 1, 0, 0 +17111,McHenry County, IL, Illinois, 6, -6:1, 240945, 1148, 1, 0, 0 +22115,Vernon Parish, LA, Louisiana, 7, -6:1, 51570, 1148, 1, 0, 0 +48349,Navarro County, TX, Texas, 7, -6:1, 41738, 1148, 1, 0, 0 +17115,Macon County, IL, Illinois, 6, -6:1, 113772, 1148, 1, 0, 0 +51003,Albemarle County, VA, Virginia, 2, -5:1, 78401, 1387, 1, 0, 0 +01087,Macon County, AL, Alabama, 3, -6:1, 22951, 1148, 1, 0, 0 +54007,Braxton County, WV, West Virginia, 2, -5:1, 13185, 1387, 1, 0, 0 +39113,Montgomery County, OH, Ohio, 4, -5:1, 558427, 1387, 1, 0, 0 +12053,Hernando County, FL, Florida, 3, -5:1, 127227, 1387, 1, 0, 0 +37189,Watauga County, NC, North Carolina, 2, -5:1, 40965, 1387, 1, 0, 0 +40009,Beckham County, OK, Oklahoma, 7, -6:1, 19584, 1148, 1, 0, 0 +18063,Hendricks County, IN, Indiana, 4, -5:1, 95146, 1387, 1, 0, 0 +42069,Lackawanna County, PA, Pennsylvania, 1, -5:1, 208455, 1387, 1, 0, 0 +56027,Niobrara County, WY, Wyoming, 8, -7:1, 2706, 4647, 1, 0, 0 +19099,Jasper County, IA, Iowa, 5, -6:1, 35961, 1148, 1, 0, 0 +55089,Ozaukee County, WI, Wisconnsin, 5, -6:1, 81076, 1148, 1, 0, 0 +37133,Onslow County, NC, North Carolina, 2, -5:1, 142358, 1387, 1, 0, 0 +38047,Logan County, ND, North Dakota, 5, -6:1, 2355, 1148, 1, 0, 0 +30061,Mineral County, MT, Montana, 6, -7:1, 3748, 4647, 1, 0, 0 +48077,Clay County, TX, Texas, 7, -6:1, 10567, 1148, 1, 0, 0 +48343,Morris County, TX, Texas, 7, -6:1, 13358, 1148, 1, 0, 0 +13263,Talbot County, GA, Georgia, 3, -5:1, 6935, 1387, 1, 0, 0 +38035,Grand Forks County, ND, North Dakota, 5, -6:1, 66869, 1148, 1, 0, 0 +28109,Pearl River County, MS, Missippi, 5, -6:1, 46862, 1148, 1, 0, 0 +48307,McCulloch County, TX, Texas, 7, -6:1, 8751, 1148, 1, 0, 0 +41023,Grant County, OR, Oregon, 9, -8:1, 8075, 3240, 0, 1, 0 +42005,Armstrong County, PA, Pennsylvania, 1, -5:1, 73181, 1387, 1, 0, 0 +31181,Webster County, NE, Nebraska, 6, -7:1, 4019, 4647, 1, 0, 0 +37171,Surry County, NC, North Carolina, 2, -5:1, 67052, 1387, 1, 0, 0 +31025,Cass County, NE, Nebraska, 6, -6:1, 24486, 1148, 1, 0, 0 +16023,Butte County, ID, Idaho, 8, -7:1, 3033, 4647, 1, 0, 0 +40087,McClain County, OK, Oklahoma, 7, -6:1, 26224, 1148, 1, 0, 0 +21151,Madison County, KY, Kentucky, 4, -5:1, 66502, 1387, 1, 0, 0 +32019,Lyon County, NV, Nevada, 8, -8:1, 30072, 3240, 0, 1, 0 +21197,Powell County, KY, Kentucky, 4, -5:1, 12945, 1387, 1, 0, 0 +13019,Berrien County, GA, Georgia, 3, -5:1, 16353, 1387, 1, 0, 0 +29147,Nodaway County, MO, Mosourri, 6, -6:1, 20777, 1148, 1, 0, 0 +13023,Bleckley County, GA, Georgia, 3, -5:1, 11185, 1387, 1, 0, 0 +42097,Northumberland County, PA, Pennsylvania, 1, -5:1, 94017, 1387, 1, 0, 0 +42123,Warren County, PA, Pennsylvania, 1, -5:1, 43910, 1387, 1, 0, 0 +31107,Knox County, NE, Nebraska, 6, -7:1, 9216, 4647, 1, 0, 0 +48473,Waller County, TX, Texas, 7, -6:1, 27218, 1148, 1, 0, 0 +48403,Sabine County, TX, Texas, 7, -6:1, 10551, 1148, 1, 0, 0 +28113,Pike County, MS, Missippi, 5, -6:1, 37920, 1148, 1, 0, 0 +08103,Rio Blanco County, CO, Colorado, 8, -7:1, 6265, 4647, 1, 0, 0 +39053,Gallia County, OH, Ohio, 4, -5:1, 33422, 1387, 1, 0, 0 +29209,Stone County, MO, Mosourri, 6, -6:1, 26807, 1148, 1, 0, 0 +46087,McCook County, SD, South Dakota, 5, -7:1, 5598, 4647, 1, 0, 0 +13107,Emanuel County, GA, Georgia, 3, -5:1, 21023, 1387, 1, 0, 0 +39125,Paulding County, OH, Ohio, 4, -5:1, 20078, 1387, 1, 0, 0 +06079,San Luis Obispo County, CA, California, 9, -8:1, 234366, 3240, 0, 1, 0 +12129,Wakulla County, FL, Florida, 3, -5:1, 18652, 1387, 1, 0, 0 +53059,Skamania County, WA, Washiington, 9, -8:1, 9805, 3240, 0, 1, 0 +13123,Gilmer County, GA, Georgia, 3, -5:1, 18672, 1387, 1, 0, 0 +45047,Greenwood County, SC, South Carolina, 2, -5:1, 63623, 1387, 1, 0, 0 +32007,Elko County, NV, Nevada, 8, -8:1, 46084, 3240, 0, 1, 0 +55019,Clark County, WI, Wisconnsin, 5, -6:1, 33147, 1148, 1, 0, 0 +13161,Jeff Davis County, GA, Georgia, 3, -5:1, 12751, 1387, 1, 0, 0 +26005,Allegan County, MI, Michigan, 4, -5:1, 101662, 1387, 1, 0, 0 +28161,Yalobusha County, MS, Missippi, 5, -6:1, 12366, 1148, 1, 0, 0 +47047,Fayette County, TN, Tennesee, 3, -5:1, 30457, 1387, 1, 0, 0 +36045,Jefferson County, NY, New York, 1, -5:1, 111050, 1387, 1, 0, 0 +29121,Macon County, MO, Mosourri, 6, -6:1, 15278, 1148, 1, 0, 0 +01043,Cullman County, AL, Alabama, 3, -6:1, 74994, 1148, 1, 0, 0 +17175,Stark County, IL, Illinois, 6, -6:1, 6290, 1148, 1, 0, 0 +47153,Sequatchie County, TN, Tennesee, 3, -6:1, 10367, 1148, 1, 0, 0 +48451,Tom Green County, TX, Texas, 7, -6:1, 102775, 1148, 1, 0, 0 +18139,Rush County, IN, Indiana, 4, -5:1, 18307, 1387, 1, 0, 0 +51680,Lynchburg city, VA, Virginia, 2, -5:1, 65473, 1387, 1, 0, 0 +23001,Androscoggin County, ME, Maine, 0, -5:1, 101280, 1387, 1, 0, 0 +38095,Towner County, ND, North Dakota, 5, -6:1, 3018, 1148, 1, 0, 0 +46019,Butte County, SD, South Dakota, 5, -6:1, 9018, 1148, 1, 0, 0 +32510,Carson City, NV, Nevada, 8, -8:1, 49301, 3240, 0, 1, 0 +46069,Hyde County, SD, South Dakota, 5, -7:1, 1605, 4647, 1, 0, 0 +05001,Arkansas County, AR, Arkansas, 7, -6:1, 20787, 1148, 1, 0, 0 +40015,Caddo County, OK, Oklahoma, 7, -6:1, 30981, 1148, 1, 0, 0 +26035,Clare County, MI, Michigan, 4, -5:1, 29578, 1387, 1, 0, 0 +21155,Marion County, KY, Kentucky, 4, -5:1, 17018, 1387, 1, 0, 0 +48127,Dimmit County, TX, Texas, 7, -6:1, 10364, 1148, 1, 0, 0 +37023,Burke County, NC, North Carolina, 2, -5:1, 82687, 1387, 1, 0, 0 +21187,Owen County, KY, Kentucky, 4, -5:1, 10264, 1387, 1, 0, 0 +42039,Crawford County, PA, Pennsylvania, 1, -5:1, 89415, 1387, 1, 0, 0 +53011,Clark County, WA, Washiington, 9, -8:1, 326943, 3240, 0, 1, 0 +39121,Noble County, OH, Ohio, 4, -5:1, 12343, 1387, 1, 0, 0 +38039,Griggs County, ND, North Dakota, 5, -6:1, 2842, 1148, 1, 0, 0 +20147,Phillips County, KS, Kansas, 6, -6:1, 6080, 1148, 1, 0, 0 +38063,Nelson County, ND, North Dakota, 5, -6:1, 3716, 1148, 1, 0, 0 +50017,Orange County, VT, Vermont, 0, -5:1, 27924, 1387, 1, 0, 0 +13293,Upson County, GA, Georgia, 3, -5:1, 27075, 1387, 1, 0, 0 +30081,Ravalli County, MT, Montana, 6, -7:1, 35156, 4647, 1, 0, 0 +48099,Coryell County, TX, Texas, 7, -6:1, 77981, 1148, 1, 0, 0 +26099,Macomb County, MI, Michigan, 4, -5:1, 787698, 1387, 1, 0, 0 +47013,Campbell County, TN, Tennesee, 3, -5:1, 38241, 1387, 1, 0, 0 +34039,Union County, NJ, New Jersey, 0, -5:1, 500608, 1387, 1, 0, 0 +21079,Garrard County, KY, Kentucky, 4, -6:1, 13916, 1148, 1, 0, 0 +41003,Benton County, OR, Oregon, 9, -8:1, 77755, 3240, 0, 1, 0 +29109,Lawrence County, MO, Mosourri, 6, -6:1, 33122, 1148, 1, 0, 0 +56017,Hot Springs County, WY, Wyoming, 8, -7:1, 4727, 4647, 1, 0, 0 +51161,Roanoke County, VA, Virginia, 2, -5:1, 80839, 1387, 1, 0, 0 +18079,Jennings County, IN, Indiana, 4, -5:1, 27789, 1387, 1, 0, 0 +47071,Hardin County, TN, Tennesee, 3, -5:1, 24961, 1387, 1, 0, 0 +48489,Willacy County, TX, Texas, 7, -6:1, 19622, 1148, 1, 0, 0 +27011,Big Stone County, MN, Minnesota, 5, -6:1, 5654, 1148, 1, 0, 0 +37125,Moore County, NC, North Carolina, 2, -5:1, 71394, 1387, 1, 0, 0 +13045,Carroll County, GA, Georgia, 3, -5:1, 83021, 1387, 1, 0, 0 +36087,Rockland County, NY, New York, 1, -5:1, 281338, 1387, 1, 0, 0 +22073,Ouachita Parish, LA, Louisiana, 7, -6:1, 146979, 1148, 1, 0, 0 +16011,Bingham County, ID, Idaho, 8, -7:1, 41820, 4647, 1, 0, 0 +48053,Burnet County, TX, Texas, 7, -6:1, 32195, 1148, 1, 0, 0 +48095,Concho County, TX, Texas, 7, -6:1, 3119, 1148, 1, 0, 0 +46091,Marshall County, SD, South Dakota, 5, -7:1, 4563, 4647, 1, 0, 0 +30043,Jefferson County, MT, Montana, 6, -7:1, 10087, 4647, 1, 0, 0 +29013,Bates County, MO, Mosourri, 6, -6:1, 15770, 1148, 1, 0, 0 +40003,Alfalfa County, OK, Oklahoma, 7, -6:1, 6044, 1148, 1, 0, 0 +21109,Jackson County, KY, Kentucky, 4, -6:1, 12908, 1148, 1, 0, 0 +40117,Pawnee County, OK, Oklahoma, 7, -6:1, 16438, 1148, 1, 0, 0 +06029,Kern County, CA, California, 9, -8:1, 631459, 3240, 0, 1, 0 +56031,Platte County, WY, Wyoming, 8, -7:1, 8626, 4647, 1, 0, 0 +28021,Claiborne County, MS, Missippi, 5, -6:1, 11662, 1148, 1, 0, 0 +13177,Lee County, GA, Georgia, 3, -5:1, 22767, 1387, 1, 0, 0 +28079,Leake County, MS, Missippi, 5, -6:1, 19372, 1148, 1, 0, 0 +36111,Ulster County, NY, New York, 1, -5:1, 166351, 1387, 1, 0, 0 +31019,Buffalo County, NE, Nebraska, 6, -6:1, 40596, 1148, 1, 0, 0 +54049,Marion County, WV, West Virginia, 2, -5:1, 56318, 1387, 1, 0, 0 +27101,Murray County, MN, Minnesota, 5, -6:1, 9517, 1148, 1, 0, 0 +19149,Plymouth County, IA, Iowa, 5, -6:1, 24825, 1148, 1, 0, 0 +16029,Caribou County, ID, Idaho, 8, -7:1, 7426, 4647, 1, 0, 0 +41053,Polk County, OR, Oregon, 9, -8:1, 61560, 3240, 0, 1, 0 +01059,Franklin County, AL, Alabama, 3, -6:1, 29682, 1148, 1, 0, 0 +16087,Washington County, ID, Idaho, 8, -7:1, 10171, 4647, 1, 0, 0 diff --git a/tpcdsgen/data/first_names.dst b/tpcdsgen/data/first_names.dst new file mode 100755 index 00000000..79c57a48 --- /dev/null +++ b/tpcdsgen/data/first_names.dst @@ -0,0 +1,5169 @@ +-- values weights +-- ==== ======= +-- 1: Name 1: frequency as a male name +-- 2: frequency as a female name +-- 3: frequency as name (gender ignored) +-- +Aaron: 240, 2, 242 +Abbey: 0, 4, 4 +Abbie: 0, 8, 8 +Abby: 0, 16, 16 +Abdul: 7, 0, 7 +Abe: 6, 0, 6 +Abel: 19, 0, 19 +Abigail: 0, 25, 25 +Abraham: 35, 0, 35 +Abram: 5, 0, 5 +Ada: 0, 57, 57 +Adah: 0, 1, 1 +Adalberto: 5, 0, 5 +Adaline: 0, 2, 2 +Adam: 259, 1, 260 +Adan: 8, 0, 8 +Addie: 0, 26, 26 +Adela: 0, 14, 14 +Adelaida: 0, 4, 4 +Adelaide: 0, 8, 8 +Adele: 0, 25, 25 +Adelia: 0, 3, 3 +Adelina: 0, 7, 7 +Adeline: 0, 18, 18 +Adell: 0, 5, 5 +Adella: 0, 3, 3 +Adelle: 0, 3, 3 +Adena: 0, 1, 1 +Adina: 0, 3, 3 +Adolfo: 14, 0, 14 +Adolph: 11, 0, 11 +Adria: 0, 3, 3 +Adrian: 69, 9, 78 +Adriana: 0, 25, 25 +Adriane: 0, 4, 4 +Adrianna: 0, 4, 4 +Adrianne: 0, 7, 7 +Adrien: 0, 1, 1 +Adriene: 0, 2, 2 +Adrienne: 0, 39, 39 +Afton: 0, 3, 3 +Agatha: 0, 7, 7 +Agnes: 0, 98, 98 +Agnus: 0, 1, 1 +Agripina: 0, 1, 1 +Agueda: 0, 1, 1 +Agustin: 15, 0, 15 +Agustina: 0, 3, 3 +Ahmad: 6, 0, 6 +Ahmed: 5, 0, 5 +Ai: 0, 1, 1 +Aida: 0, 23, 23 +Aide: 0, 1, 1 +Aiko: 0, 1, 1 +Aileen: 0, 16, 16 +Ailene: 0, 1, 1 +Aimee: 0, 27, 27 +Aisha: 0, 9, 9 +Aja: 0, 3, 3 +Akiko: 0, 2, 2 +Akilah: 0, 1, 1 +Al: 23, 0, 23 +Alaina: 0, 5, 5 +Alaine: 0, 1, 1 +Alan: 204, 0, 204 +Alana: 0, 12, 12 +Alane: 0, 1, 1 +Alanna: 0, 4, 4 +Alayna: 0, 1, 1 +Alba: 0, 9, 9 +Albert: 314, 1, 315 +Alberta: 0, 52, 52 +Albertha: 0, 3, 3 +Albertina: 0, 2, 2 +Albertine: 0, 3, 3 +Alberto: 53, 0, 53 +Albina: 0, 4, 4 +Alda: 0, 4, 4 +Alden: 6, 0, 6 +Aldo: 7, 0, 7 +Alease: 0, 1, 1 +Alec: 6, 0, 6 +Alecia: 0, 7, 7 +Aleen: 0, 1, 1 +Aleida: 0, 2, 2 +Aleisha: 0, 1, 1 +Alejandra: 0, 12, 12 +Alejandrina: 0, 2, 2 +Alejandro: 43, 0, 43 +Alena: 0, 3, 3 +Alene: 0, 7, 7 +Alesha: 0, 4, 4 +Aleshia: 0, 1, 1 +Alesia: 0, 4, 4 +Alessandra: 0, 1, 1 +Aleta: 0, 4, 4 +Aletha: 0, 6, 6 +Alethea: 0, 3, 3 +Alethia: 0, 2, 2 +Alex: 115, 2, 117 +Alexa: 0, 6, 6 +Alexander: 132, 2, 134 +Alexandra: 0, 39, 39 +Alexandria: 0, 14, 14 +Alexia: 0, 3, 3 +Alexis: 11, 30, 41 +Alfonso: 38, 0, 38 +Alfonzo: 6, 0, 6 +Alfred: 162, 0, 162 +Alfreda: 0, 9, 9 +Alfredia: 0, 1, 1 +Alfredo: 54, 0, 54 +Ali: 12, 2, 14 +Alia: 0, 2, 2 +Alica: 0, 2, 2 +Alice: 0, 357, 357 +Alicia: 0, 146, 146 +Alida: 0, 3, 3 +Alina: 0, 6, 6 +Aline: 0, 11, 11 +Alisa: 0, 20, 20 +Alise: 0, 2, 2 +Alisha: 0, 27, 27 +Alishia: 0, 2, 2 +Alisia: 0, 2, 2 +Alison: 0, 50, 50 +Alissa: 0, 11, 11 +Alita: 0, 1, 1 +Alix: 0, 2, 2 +Aliza: 0, 2, 2 +Alla: 0, 2, 2 +Allan: 61, 0, 61 +Alleen: 0, 1, 1 +Allegra: 0, 2, 2 +Allen: 174, 2, 176 +Allena: 0, 1, 1 +Allene: 0, 8, 8 +Allie: 0, 13, 13 +Alline: 0, 2, 2 +Allison: 0, 92, 92 +Allyn: 0, 1, 1 +Allyson: 0, 13, 13 +Alma: 0, 111, 111 +Almeda: 0, 3, 3 +Almeta: 0, 2, 2 +Alona: 0, 1, 1 +Alonso: 4, 0, 4 +Alonzo: 22, 0, 22 +Alpha: 0, 5, 5 +Alphonse: 7, 0, 7 +Alphonso: 11, 0, 11 +Alta: 0, 19, 19 +Altagracia: 0, 4, 4 +Altha: 0, 2, 2 +Althea: 0, 12, 12 +Alton: 33, 0, 33 +Alva: 7, 6, 13 +Alvaro: 12, 0, 12 +Alvera: 0, 2, 2 +Alverta: 0, 1, 1 +Alvin: 105, 0, 105 +Alvina: 0, 6, 6 +Alyce: 0, 10, 10 +Alycia: 0, 3, 3 +Alysa: 0, 1, 1 +Alyse: 0, 2, 2 +Alysha: 0, 2, 2 +Alysia: 0, 3, 3 +Alyson: 0, 9, 9 +Alyssa: 0, 31, 31 +Amada: 0, 3, 3 +Amado: 5, 0, 5 +Amal: 0, 1, 1 +Amalia: 0, 10, 10 +Amanda: 0, 404, 404 +Amber: 0, 160, 160 +Amberly: 0, 1, 1 +Ambrose: 4, 0, 4 +Amee: 0, 1, 1 +Amelia: 0, 52, 52 +America: 0, 3, 3 +Ami: 0, 4, 4 +Amie: 0, 13, 13 +Amiee: 0, 1, 1 +Amina: 0, 2, 2 +Amira: 0, 1, 1 +Ammie: 0, 2, 2 +Amos: 20, 0, 20 +Amparo: 0, 9, 9 +Amy: 0, 451, 451 +An: 0, 1, 1 +Ana: 0, 120, 120 +Anabel: 0, 4, 4 +Analisa: 0, 1, 1 +Anamaria: 0, 1, 1 +Anastacia: 0, 3, 3 +Anastasia: 0, 10, 10 +Andera: 0, 1, 1 +Anderson: 7, 0, 7 +Andra: 0, 5, 5 +Andre: 76, 2, 78 +Andrea: 6, 236, 242 +Andreas: 4, 0, 4 +Andree: 0, 2, 2 +Andres: 34, 0, 34 +Andrew: 537, 2, 539 +Andria: 0, 5, 5 +Andy: 49, 0, 49 +Anette: 0, 2, 2 +Angel: 82, 35, 117 +Angela: 0, 468, 468 +Angele: 0, 2, 2 +Angelena: 0, 2, 2 +Angeles: 0, 2, 2 +Angelia: 0, 17, 17 +Angelic: 0, 2, 2 +Angelica: 0, 39, 39 +Angelika: 0, 2, 2 +Angelina: 0, 37, 37 +Angeline: 0, 17, 17 +Angelique: 0, 9, 9 +Angelita: 0, 9, 9 +Angella: 0, 3, 3 +Angelo: 39, 2, 41 +Angelyn: 0, 1, 1 +Angie: 0, 54, 54 +Angila: 0, 1, 1 +Angla: 0, 1, 1 +Angle: 0, 2, 2 +Anglea: 0, 3, 3 +Anh: 0, 4, 4 +Anibal: 5, 0, 5 +Anika: 0, 3, 3 +Anisa: 0, 1, 1 +Anisha: 0, 1, 1 +Anissa: 0, 5, 5 +Anita: 0, 162, 162 +Anitra: 0, 3, 3 +Anja: 0, 1, 1 +Anjanette: 0, 2, 2 +Anjelica: 0, 1, 1 +Ann: 0, 364, 364 +Anna: 0, 440, 440 +Annabel: 0, 3, 3 +Annabell: 0, 2, 2 +Annabelle: 0, 12, 12 +Annalee: 0, 2, 2 +Annalisa: 0, 2, 2 +Annamae: 0, 2, 2 +Annamaria: 0, 1, 1 +Annamarie: 0, 5, 5 +Anne: 0, 228, 228 +Anneliese: 0, 3, 3 +Annelle: 0, 1, 1 +Annemarie: 0, 6, 6 +Annett: 0, 2, 2 +Annetta: 0, 6, 6 +Annette: 0, 125, 125 +Annice: 0, 2, 2 +Annie: 0, 216, 216 +Annika: 0, 1, 1 +Annis: 0, 2, 2 +Annita: 0, 1, 1 +Annmarie: 0, 9, 9 +Anthony: 721, 3, 724 +Antione: 4, 0, 4 +Antionette: 0, 8, 8 +Antoine: 16, 0, 16 +Antoinette: 0, 48, 48 +Anton: 13, 0, 13 +Antone: 5, 0, 5 +Antonetta: 0, 1, 1 +Antonette: 0, 5, 5 +Antonia: 4, 35, 39 +Antonietta: 0, 2, 2 +Antonina: 0, 3, 3 +Antonio: 190, 2, 192 +Antony: 8, 0, 8 +Antwan: 6, 0, 6 +Anya: 0, 2, 2 +Apolonia: 0, 1, 1 +April: 0, 154, 154 +Apryl: 0, 2, 2 +Ara: 0, 1, 1 +Araceli: 0, 10, 10 +Aracelis: 0, 2, 2 +Aracely: 0, 4, 4 +Arcelia: 0, 3, 3 +Archie: 33, 0, 33 +Ardath: 0, 1, 1 +Ardelia: 0, 1, 1 +Ardell: 0, 2, 2 +Ardella: 0, 2, 2 +Ardelle: 0, 1, 1 +Arden: 4, 0, 4 +Ardis: 0, 4, 4 +Ardith: 0, 4, 4 +Aretha: 0, 6, 6 +Argelia: 0, 1, 1 +Argentina: 0, 1, 1 +Ariana: 0, 5, 5 +Ariane: 0, 2, 2 +Arianna: 0, 2, 2 +Arianne: 0, 1, 1 +Arica: 0, 1, 1 +Arie: 0, 2, 2 +Ariel: 7, 7, 14 +Arielle: 0, 3, 3 +Arla: 0, 2, 2 +Arlean: 0, 1, 1 +Arleen: 0, 7, 7 +Arlen: 4, 0, 4 +Arlena: 0, 2, 2 +Arlene: 0, 94, 94 +Arletha: 0, 1, 1 +Arletta: 0, 2, 2 +Arlette: 0, 3, 3 +Arlie: 4, 0, 4 +Arlinda: 0, 1, 1 +Arline: 0, 10, 10 +Arlyne: 0, 1, 1 +Armand: 12, 0, 12 +Armanda: 0, 2, 2 +Armandina: 0, 1, 1 +Armando: 58, 0, 58 +Armida: 0, 4, 4 +Arminda: 0, 2, 2 +Arnetta: 0, 2, 2 +Arnette: 0, 1, 1 +Arnita: 0, 2, 2 +Arnold: 72, 0, 72 +Arnoldo: 4, 0, 4 +Arnulfo: 7, 0, 7 +Aron: 6, 0, 6 +Arron: 8, 0, 8 +Art: 9, 0, 9 +Arthur: 335, 2, 337 +Artie: 0, 4, 4 +Arturo: 43, 0, 43 +Arvilla: 0, 2, 2 +Asa: 4, 0, 4 +Asha: 0, 3, 3 +Ashanti: 0, 1, 1 +Ashely: 0, 3, 3 +Ashlea: 0, 1, 1 +Ashlee: 0, 13, 13 +Ashleigh: 0, 8, 8 +Ashley: 14, 303, 317 +Ashli: 0, 2, 2 +Ashlie: 0, 3, 3 +Ashly: 0, 3, 3 +Ashlyn: 0, 2, 2 +Ashton: 0, 4, 4 +Asia: 0, 5, 5 +Asley: 0, 1, 1 +Assunta: 0, 2, 2 +Astrid: 0, 4, 4 +Asuncion: 0, 2, 2 +Athena: 0, 7, 7 +Aubrey: 19, 0, 19 +Audie: 0, 2, 2 +Audra: 0, 15, 15 +Audrea: 0, 2, 2 +Audrey: 0, 127, 127 +Audria: 0, 1, 1 +Audrie: 0, 1, 1 +Audry: 0, 3, 3 +August: 15, 0, 15 +Augusta: 0, 10, 10 +Augustina: 0, 2, 2 +Augustine: 7, 0, 7 +Augustus: 5, 0, 5 +Aundrea: 0, 2, 2 +Aura: 0, 5, 5 +Aurea: 0, 3, 3 +Aurelia: 0, 10, 10 +Aurelio: 9, 0, 9 +Aurora: 0, 24, 24 +Aurore: 0, 1, 1 +Austin: 44, 0, 44 +Autumn: 0, 17, 17 +Ava: 0, 14, 14 +Avelina: 0, 2, 2 +Avery: 11, 0, 11 +Avis: 0, 13, 13 +Avril: 0, 1, 1 +Awilda: 0, 3, 3 +Ayako: 0, 1, 1 +Ayana: 0, 2, 2 +Ayanna: 0, 2, 2 +Ayesha: 0, 3, 3 +Azalee: 0, 1, 1 +Azucena: 0, 2, 2 +Azzie: 0, 1, 1 +Babara: 0, 2, 2 +Babette: 0, 2, 2 +Bailey: 0, 3, 3 +Bambi: 0, 4, 4 +Bao: 0, 1, 1 +Barabara: 0, 1, 1 +Barb: 0, 6, 6 +Barbar: 0, 2, 2 +Barbara: 0, 980, 980 +Barbera: 0, 2, 2 +Barbie: 0, 3, 3 +Barbra: 0, 15, 15 +Bari: 0, 1, 1 +Barney: 9, 0, 9 +Barrett: 5, 0, 5 +Barrie: 0, 1, 1 +Barry: 134, 0, 134 +Bart: 14, 0, 14 +Barton: 6, 0, 6 +Basil: 10, 0, 10 +Basilia: 0, 1, 1 +Bea: 0, 3, 3 +Beata: 0, 1, 1 +Beatrice: 0, 130, 130 +Beatris: 0, 1, 1 +Beatriz: 0, 18, 18 +Beau: 9, 0, 9 +Beaulah: 0, 1, 1 +Bebe: 0, 1, 1 +Becki: 0, 1, 1 +Beckie: 0, 2, 2 +Becky: 0, 66, 66 +Bee: 0, 1, 1 +Belen: 0, 5, 5 +Belia: 0, 2, 2 +Belinda: 0, 59, 59 +Belkis: 0, 1, 1 +Bell: 0, 1, 1 +Bella: 0, 6, 6 +Belle: 0, 7, 7 +Belva: 0, 6, 6 +Ben: 78, 0, 78 +Benedict: 4, 0, 4 +Benita: 0, 13, 13 +Benito: 15, 0, 15 +Benjamin: 270, 0, 270 +Bennett: 8, 0, 8 +Bennie: 32, 0, 32 +Benny: 30, 0, 30 +Benton: 4, 0, 4 +Berenice: 0, 3, 3 +Berna: 0, 1, 1 +Bernadette: 0, 39, 39 +Bernadine: 0, 14, 14 +Bernard: 127, 0, 127 +Bernarda: 0, 1, 1 +Bernardina: 0, 1, 1 +Bernardine: 0, 1, 1 +Bernardo: 10, 0, 10 +Berneice: 0, 2, 2 +Bernetta: 0, 2, 2 +Bernice: 0, 128, 128 +Bernie: 8, 0, 8 +Berniece: 0, 6, 6 +Bernita: 0, 4, 4 +Berry: 6, 0, 6 +Bert: 22, 0, 22 +Berta: 0, 10, 10 +Bertha: 0, 143, 143 +Bertie: 0, 9, 9 +Bertram: 5, 0, 5 +Beryl: 0, 9, 9 +Bess: 0, 7, 7 +Bessie: 0, 96, 96 +Beth: 0, 110, 110 +Bethanie: 0, 1, 1 +Bethann: 0, 2, 2 +Bethany: 0, 39, 39 +Bethel: 0, 2, 2 +Betsey: 0, 2, 2 +Betsy: 0, 34, 34 +Bette: 0, 19, 19 +Bettie: 0, 23, 23 +Bettina: 0, 6, 6 +Betty: 0, 666, 666 +Bettyann: 0, 1, 1 +Bettye: 0, 15, 15 +Beula: 0, 1, 1 +Beulah: 0, 48, 48 +Bev: 0, 2, 2 +Beverlee: 0, 2, 2 +Beverley: 0, 8, 8 +Beverly: 0, 267, 267 +Bianca: 0, 15, 15 +Bibi: 0, 1, 1 +Bill: 112, 0, 112 +Billi: 0, 1, 1 +Billie: 17, 0, 17 +Billy: 248, 0, 248 +Billye: 0, 2, 2 +Birdie: 0, 7, 7 +Birgit: 0, 2, 2 +Blaine: 15, 0, 15 +Blair: 8, 0, 8 +Blake: 36, 0, 36 +Blanca: 0, 41, 41 +Blanch: 0, 4, 4 +Blanche: 0, 55, 55 +Blondell: 0, 1, 1 +Blossom: 0, 1, 1 +Blythe: 0, 2, 2 +Bo: 5, 0, 5 +Bob: 55, 0, 55 +Bobbi: 0, 16, 16 +Bobbie: 10, 0, 10 +Bobby: 223, 0, 223 +Bobbye: 0, 3, 3 +Bobette: 0, 1, 1 +Bok: 0, 1, 1 +Bong: 0, 1, 1 +Bonita: 0, 26, 26 +Bonnie: 0, 223, 223 +Bonny: 0, 4, 4 +Booker: 8, 0, 8 +Boris: 6, 0, 6 +Boyce: 4, 0, 4 +Boyd: 19, 0, 19 +Brad: 73, 0, 73 +Bradford: 21, 0, 21 +Bradley: 159, 0, 159 +Bradly: 5, 0, 5 +Brady: 14, 0, 14 +Brain: 13, 0, 13 +Branda: 0, 2, 2 +Brande: 0, 1, 1 +Brandee: 0, 3, 3 +Branden: 8, 0, 8 +Brandi: 0, 55, 55 +Brandie: 0, 8, 8 +Brandon: 260, 0, 260 +Brandy: 0, 70, 70 +Brant: 6, 0, 6 +Breana: 0, 1, 1 +Breann: 0, 1, 1 +Breanna: 0, 6, 6 +Breanne: 0, 3, 3 +Bree: 0, 3, 3 +Brenda: 0, 455, 455 +Brendan: 19, 0, 19 +Brendon: 5, 0, 5 +Brenna: 0, 5, 5 +Brent: 90, 0, 90 +Brenton: 6, 0, 6 +Bret: 18, 0, 18 +Brett: 82, 0, 82 +Brian: 736, 0, 736 +Briana: 0, 10, 10 +Brianna: 0, 14, 14 +Brianne: 0, 7, 7 +Brice: 5, 0, 5 +Bridget: 0, 45, 45 +Bridgett: 0, 9, 9 +Bridgette: 0, 15, 15 +Brigette: 0, 4, 4 +Brigid: 0, 2, 2 +Brigida: 0, 2, 2 +Brigitte: 0, 9, 9 +Brinda: 0, 2, 2 +Britany: 0, 2, 2 +Britney: 0, 9, 9 +Britni: 0, 2, 2 +Britt: 4, 0, 4 +Britta: 0, 1, 1 +Brittaney: 0, 1, 1 +Brittani: 0, 4, 4 +Brittanie: 0, 1, 1 +Brittany: 0, 117, 117 +Britteny: 0, 1, 1 +Brittney: 0, 24, 24 +Brittni: 0, 2, 2 +Brittny: 0, 1, 1 +Brock: 9, 0, 9 +Broderick: 4, 0, 4 +Bronwyn: 0, 2, 2 +Brook: 0, 4, 4 +Brooke: 0, 39, 39 +Brooks: 7, 0, 7 +Bruce: 263, 0, 263 +Bruna: 0, 1, 1 +Brunilda: 0, 2, 2 +Bruno: 10, 0, 10 +Bryan: 190, 0, 190 +Bryanna: 0, 1, 1 +Bryant: 26, 0, 26 +Bryce: 16, 0, 16 +Brynn: 0, 2, 2 +Bryon: 12, 0, 12 +Buck: 4, 0, 4 +Bud: 5, 0, 5 +Buddy: 14, 0, 14 +Buena: 0, 1, 1 +Buffy: 0, 3, 3 +Buford: 8, 0, 8 +Bula: 0, 1, 1 +Bulah: 0, 1, 1 +Bunny: 0, 1, 1 +Burl: 5, 0, 5 +Burma: 0, 1, 1 +Burt: 6, 0, 6 +Burton: 14, 0, 14 +Buster: 4, 0, 4 +Byron: 52, 0, 52 +Caitlin: 0, 23, 23 +Caitlyn: 0, 4, 4 +Calandra: 0, 1, 1 +Caleb: 23, 0, 23 +Calista: 0, 1, 1 +Callie: 0, 16, 16 +Calvin: 115, 0, 115 +Camelia: 0, 2, 2 +Camellia: 0, 1, 1 +Cameron: 37, 0, 37 +Cami: 0, 3, 3 +Camie: 0, 1, 1 +Camila: 0, 1, 1 +Camilla: 0, 8, 8 +Camille: 0, 28, 28 +Cammie: 0, 2, 2 +Cammy: 0, 1, 1 +Candace: 0, 51, 51 +Candance: 0, 3, 3 +Candelaria: 0, 4, 4 +Candi: 0, 5, 5 +Candice: 0, 46, 46 +Candida: 0, 6, 6 +Candie: 0, 2, 2 +Candis: 0, 3, 3 +Candra: 0, 1, 1 +Candy: 0, 22, 22 +Candyce: 0, 2, 2 +Caprice: 0, 2, 2 +Cara: 0, 25, 25 +Caren: 0, 7, 7 +Carey: 13, 0, 13 +Cari: 0, 7, 7 +Caridad: 0, 6, 6 +Carie: 0, 2, 2 +Carin: 0, 3, 3 +Carina: 0, 4, 4 +Carisa: 0, 2, 2 +Carissa: 0, 9, 9 +Carita: 0, 1, 1 +Carl: 346, 0, 346 +Carla: 0, 107, 107 +Carlee: 0, 1, 1 +Carleen: 0, 4, 4 +Carlena: 0, 1, 1 +Carlene: 0, 12, 12 +Carletta: 0, 2, 2 +Carley: 0, 2, 2 +Carli: 0, 1, 1 +Carlie: 0, 2, 2 +Carline: 0, 2, 2 +Carlita: 0, 1, 1 +Carlo: 9, 0, 9 +Carlos: 229, 0, 229 +Carlota: 0, 3, 3 +Carlotta: 0, 4, 4 +Carlton: 37, 0, 37 +Carly: 0, 10, 10 +Carlyn: 0, 3, 3 +Carma: 0, 3, 3 +Carman: 0, 4, 4 +Carmel: 0, 6, 6 +Carmela: 0, 18, 18 +Carmelia: 0, 1, 1 +Carmelina: 0, 2, 2 +Carmelita: 0, 6, 6 +Carmella: 0, 12, 12 +Carmelo: 9, 0, 9 +Carmen: 11, 0, 11 +Carmina: 0, 1, 1 +Carmine: 7, 0, 7 +Carmon: 0, 2, 2 +Carol: 6, 0, 6 +Carola: 0, 1, 1 +Carolann: 0, 3, 3 +Carole: 0, 71, 71 +Carolee: 0, 4, 4 +Carolin: 0, 1, 1 +Carolina: 0, 21, 21 +Caroline: 0, 85, 85 +Caroll: 0, 1, 1 +Carolyn: 0, 385, 385 +Carolyne: 0, 2, 2 +Carolynn: 0, 3, 3 +Caron: 0, 3, 3 +Caroyln: 0, 1, 1 +Carri: 0, 3, 3 +Carrie: 0, 171, 171 +Carrol: 4, 0, 4 +Carroll: 26, 0, 26 +Carry: 0, 1, 1 +Carson: 8, 0, 8 +Carter: 9, 0, 9 +Cary: 19, 0, 19 +Caryl: 0, 4, 4 +Carylon: 0, 1, 1 +Caryn: 0, 8, 8 +Casandra: 0, 9, 9 +Casey: 54, 0, 54 +Casie: 0, 2, 2 +Casimira: 0, 1, 1 +Cassandra: 0, 72, 72 +Cassaundra: 0, 1, 1 +Cassey: 0, 1, 1 +Cassi: 0, 1, 1 +Cassidy: 0, 3, 3 +Cassie: 0, 23, 23 +Cassondra: 0, 1, 1 +Cassy: 0, 1, 1 +Catalina: 0, 14, 14 +Catarina: 0, 2, 2 +Caterina: 0, 2, 2 +Catharine: 0, 6, 6 +Catherin: 0, 1, 1 +Catherina: 0, 1, 1 +Catherine: 0, 373, 373 +Cathern: 0, 1, 1 +Catheryn: 0, 1, 1 +Cathey: 0, 2, 2 +Cathi: 0, 3, 3 +Cathie: 0, 4, 4 +Cathleen: 0, 17, 17 +Cathrine: 0, 6, 6 +Cathryn: 0, 9, 9 +Cathy: 0, 137, 137 +Catina: 0, 4, 4 +Catrice: 0, 1, 1 +Catrina: 0, 7, 7 +Cayla: 0, 1, 1 +Cecelia: 0, 32, 32 +Cecil: 78, 0, 78 +Cecila: 0, 2, 2 +Cecile: 0, 19, 19 +Cecilia: 0, 55, 55 +Cecille: 0, 1, 1 +Cecily: 0, 3, 3 +Cedric: 29, 0, 29 +Cedrick: 4, 0, 4 +Celena: 0, 2, 2 +Celesta: 0, 1, 1 +Celeste: 0, 25, 25 +Celestina: 0, 2, 2 +Celestine: 0, 7, 7 +Celia: 0, 44, 44 +Celina: 0, 8, 8 +Celinda: 0, 1, 1 +Celine: 0, 2, 2 +Celsa: 0, 1, 1 +Ceola: 0, 1, 1 +Cesar: 34, 0, 34 +Chad: 165, 0, 165 +Chadwick: 7, 0, 7 +Chae: 0, 1, 1 +Chan: 0, 1, 1 +Chana: 0, 3, 3 +Chance: 7, 0, 7 +Chanda: 0, 4, 4 +Chandra: 0, 14, 14 +Chanel: 0, 5, 5 +Chanell: 0, 1, 1 +Chanelle: 0, 1, 1 +Chang: 5, 0, 5 +Chantal: 0, 5, 5 +Chantay: 0, 1, 1 +Chante: 0, 2, 2 +Chantel: 0, 7, 7 +Chantell: 0, 2, 2 +Chantelle: 0, 3, 3 +Chara: 0, 1, 1 +Charis: 0, 1, 1 +Charise: 0, 2, 2 +Charissa: 0, 3, 3 +Charisse: 0, 4, 4 +Charita: 0, 1, 1 +Charity: 0, 18, 18 +Charla: 0, 6, 6 +Charleen: 0, 5, 5 +Charlena: 0, 1, 1 +Charlene: 0, 97, 97 +Charles: 1523, 0, 1523 +Charlesetta: 0, 1, 1 +Charlette: 0, 3, 3 +Charley: 9, 0, 9 +Charlie: 90, 0, 90 +Charline: 0, 5, 5 +Charlott: 0, 1, 1 +Charlotte: 0, 169, 169 +Charlsie: 0, 1, 1 +Charlyn: 0, 1, 1 +Charmain: 0, 1, 1 +Charmaine: 0, 11, 11 +Charolette: 0, 3, 3 +Chas: 4, 0, 4 +Chase: 16, 0, 16 +Chasidy: 0, 1, 1 +Chasity: 0, 10, 10 +Chassidy: 0, 1, 1 +Chastity: 0, 5, 5 +Chau: 0, 2, 2 +Chauncey: 5, 0, 5 +Chaya: 0, 2, 2 +Chelsea: 0, 38, 38 +Chelsey: 0, 8, 8 +Chelsie: 0, 4, 4 +Cher: 0, 1, 1 +Chere: 0, 1, 1 +Cheree: 0, 1, 1 +Cherelle: 0, 1, 1 +Cheri: 0, 25, 25 +Cherie: 0, 22, 22 +Cherilyn: 0, 1, 1 +Cherise: 0, 3, 3 +Cherish: 0, 2, 2 +Cherly: 0, 4, 4 +Cherlyn: 0, 1, 1 +Cherri: 0, 3, 3 +Cherrie: 0, 3, 3 +Cherry: 0, 9, 9 +Cherryl: 0, 2, 2 +Chery: 0, 1, 1 +Cheryl: 0, 315, 315 +Cheryle: 0, 4, 4 +Cheryll: 0, 2, 2 +Chester: 78, 0, 78 +Chet: 5, 0, 5 +Cheyenne: 0, 3, 3 +Chi: 5, 0, 5 +Chia: 0, 1, 1 +Chieko: 0, 1, 1 +Chin: 0, 2, 2 +China: 0, 1, 1 +Ching: 0, 1, 1 +Chiquita: 0, 5, 5 +Chloe: 0, 7, 7 +Chong: 4, 0, 4 +Chris: 197, 0, 197 +Chrissy: 0, 4, 4 +Christa: 0, 23, 23 +Christal: 0, 6, 6 +Christeen: 0, 2, 2 +Christel: 0, 3, 3 +Christen: 0, 7, 7 +Christena: 0, 2, 2 +Christene: 0, 3, 3 +Christi: 0, 17, 17 +Christia: 0, 1, 1 +Christian: 65, 0, 65 +Christiana: 0, 3, 3 +Christiane: 0, 3, 3 +Christie: 0, 34, 34 +Christin: 0, 7, 7 +Christina: 0, 275, 275 +Christine: 0, 382, 382 +Christinia: 0, 1, 1 +Christoper: 6, 0, 6 +Christopher: 1035, 0, 1035 +Christy: 0, 77, 77 +Chrystal: 0, 11, 11 +Chu: 0, 1, 1 +Chuck: 16, 0, 16 +Chun: 0, 3, 3 +Chung: 5, 0, 5 +Ciara: 0, 3, 3 +Cicely: 0, 2, 2 +Ciera: 0, 2, 2 +Cierra: 0, 3, 3 +Cinda: 0, 3, 3 +Cinderella: 0, 1, 1 +Cindi: 0, 7, 7 +Cindie: 0, 1, 1 +Cindy: 0, 192, 192 +Cinthia: 0, 4, 4 +Cira: 0, 1, 1 +Clair: 8, 0, 8 +Claire: 0, 61, 61 +Clara: 0, 153, 153 +Clare: 0, 12, 12 +Clarence: 197, 0, 197 +Claretha: 0, 1, 1 +Claretta: 0, 1, 1 +Claribel: 0, 3, 3 +Clarice: 0, 18, 18 +Clarinda: 0, 1, 1 +Clarine: 0, 2, 2 +Claris: 0, 1, 1 +Clarisa: 0, 1, 1 +Clarissa: 0, 14, 14 +Clarita: 0, 2, 2 +Clark: 26, 0, 26 +Classie: 0, 1, 1 +Claud: 4, 0, 4 +Claude: 68, 0, 68 +Claudette: 0, 17, 17 +Claudia: 0, 90, 90 +Claudie: 0, 1, 1 +Claudine: 0, 11, 11 +Claudio: 6, 0, 6 +Clay: 21, 0, 21 +Clayton: 60, 0, 60 +Clelia: 0, 1, 1 +Clemencia: 0, 1, 1 +Clement: 10, 0, 10 +Clemente: 4, 0, 4 +Clementina: 0, 2, 2 +Clementine: 0, 5, 5 +Clemmie: 0, 2, 2 +Cleo: 9, 0, 9 +Cleopatra: 0, 2, 2 +Cleora: 0, 2, 2 +Cleotilde: 0, 1, 1 +Cleta: 0, 3, 3 +Cletus: 5, 0, 5 +Cleveland: 16, 0, 16 +Cliff: 12, 0, 12 +Clifford: 123, 0, 123 +Clifton: 50, 0, 50 +Clint: 24, 0, 24 +Clinton: 65, 0, 65 +Clora: 0, 1, 1 +Clorinda: 0, 1, 1 +Clotilde: 0, 2, 2 +Clyde: 95, 0, 95 +Codi: 0, 1, 1 +Cody: 63, 0, 63 +Colby: 9, 0, 9 +Cole: 10, 0, 10 +Coleen: 0, 9, 9 +Coleman: 6, 0, 6 +Colene: 0, 1, 1 +Coletta: 0, 2, 2 +Colette: 0, 11, 11 +Colin: 31, 0, 31 +Colleen: 0, 92, 92 +Collen: 0, 2, 2 +Collene: 0, 1, 1 +Collette: 0, 5, 5 +Collin: 9, 0, 9 +Colton: 4, 0, 4 +Columbus: 5, 0, 5 +Concepcion: 0, 14, 14 +Conception: 0, 1, 1 +Concetta: 0, 9, 9 +Concha: 0, 1, 1 +Conchita: 0, 2, 2 +Connie: 5, 0, 5 +Conrad: 22, 0, 22 +Constance: 0, 91, 91 +Consuela: 0, 2, 2 +Consuelo: 0, 22, 22 +Contessa: 0, 1, 1 +Cora: 0, 58, 58 +Coral: 0, 4, 4 +Coralee: 0, 1, 1 +Coralie: 0, 1, 1 +Corazon: 0, 3, 3 +Cordelia: 0, 5, 5 +Cordell: 4, 0, 4 +Cordia: 0, 1, 1 +Cordie: 0, 2, 2 +Coreen: 0, 2, 2 +Corene: 0, 3, 3 +Coretta: 0, 2, 2 +Corey: 98, 0, 98 +Cori: 0, 6, 6 +Corie: 0, 2, 2 +Corina: 0, 10, 10 +Corine: 0, 11, 11 +Corinna: 0, 4, 4 +Corinne: 0, 24, 24 +Corliss: 0, 2, 2 +Cornelia: 0, 15, 15 +Cornelius: 21, 0, 21 +Cornell: 8, 0, 8 +Corrie: 0, 5, 5 +Corrin: 0, 1, 1 +Corrina: 0, 3, 3 +Corrine: 0, 14, 14 +Corrinne: 0, 1, 1 +Cortez: 4, 0, 4 +Cortney: 0, 8, 8 +Cory: 68, 0, 68 +Courtney: 19, 0, 19 +Coy: 10, 0, 10 +Craig: 206, 0, 206 +Creola: 0, 1, 1 +Cris: 0, 1, 1 +Criselda: 0, 2, 2 +Crissy: 0, 2, 2 +Crista: 0, 2, 2 +Cristal: 0, 5, 5 +Cristen: 0, 2, 2 +Cristi: 0, 3, 3 +Cristie: 0, 2, 2 +Cristin: 0, 3, 3 +Cristina: 0, 33, 33 +Cristine: 0, 4, 4 +Cristobal: 4, 0, 4 +Cristopher: 4, 0, 4 +Cristy: 0, 6, 6 +Cruz: 9, 0, 9 +Crysta: 0, 1, 1 +Crystal: 0, 207, 207 +Crystle: 0, 1, 1 +Cuc: 0, 1, 1 +Curt: 15, 0, 15 +Curtis: 180, 0, 180 +Cyndi: 0, 3, 3 +Cyndy: 0, 1, 1 +Cynthia: 0, 469, 469 +Cyril: 7, 0, 7 +Cyrstal: 0, 1, 1 +Cyrus: 7, 0, 7 +Cythia: 0, 3, 3 +Dacia: 0, 1, 1 +Dagmar: 0, 2, 2 +Dagny: 0, 1, 1 +Dahlia: 0, 2, 2 +Daina: 0, 1, 1 +Daine: 0, 1, 1 +Daisey: 0, 2, 2 +Daisy: 0, 62, 62 +Dakota: 0, 1, 1 +Dale: 184, 0, 184 +Dalene: 0, 1, 1 +Dalia: 0, 6, 6 +Dalila: 0, 2, 2 +Dallas: 24, 0, 24 +Dalton: 8, 0, 8 +Damaris: 0, 6, 6 +Damian: 16, 0, 16 +Damien: 14, 0, 14 +Damion: 5, 0, 5 +Damon: 34, 0, 34 +Dan: 101, 0, 101 +Dana: 42, 0, 42 +Danae: 0, 2, 2 +Dane: 12, 0, 12 +Danelle: 0, 5, 5 +Danette: 0, 7, 7 +Dani: 0, 3, 3 +Dania: 0, 2, 2 +Danial: 8, 0, 8 +Danica: 0, 3, 3 +Daniel: 974, 0, 974 +Daniela: 0, 7, 7 +Daniele: 0, 3, 3 +Daniell: 0, 1, 1 +Daniella: 0, 4, 4 +Danielle: 0, 149, 149 +Danika: 0, 2, 2 +Danille: 0, 1, 1 +Danilo: 4, 0, 4 +Danita: 0, 5, 5 +Dann: 0, 1, 1 +Danna: 0, 7, 7 +Dannette: 0, 2, 2 +Dannie: 5, 0, 5 +Dannielle: 0, 3, 3 +Danny: 190, 0, 190 +Dante: 10, 0, 10 +Danuta: 0, 2, 2 +Danyel: 0, 1, 1 +Danyell: 0, 1, 1 +Danyelle: 0, 3, 3 +Daphine: 0, 2, 2 +Daphne: 0, 19, 19 +Dara: 0, 7, 7 +Darby: 0, 2, 2 +Darcel: 0, 1, 1 +Darcey: 0, 1, 1 +Darci: 0, 4, 4 +Darcie: 0, 3, 3 +Darcy: 0, 12, 12 +Darell: 4, 0, 4 +Daren: 8, 0, 8 +Daria: 0, 4, 4 +Darin: 20, 0, 20 +Dario: 5, 0, 5 +Darius: 13, 0, 13 +Darla: 0, 29, 29 +Darleen: 0, 5, 5 +Darlena: 0, 1, 1 +Darlene: 0, 142, 142 +Darline: 0, 3, 3 +Darnell: 20, 0, 20 +Daron: 4, 0, 4 +Darrel: 22, 0, 22 +Darrell: 108, 0, 108 +Darren: 64, 0, 64 +Darrick: 5, 0, 5 +Darrin: 19, 0, 19 +Darron: 4, 0, 4 +Darryl: 67, 0, 67 +Darwin: 14, 0, 14 +Daryl: 50, 0, 50 +Dave: 53, 0, 53 +David: 2363, 0, 2363 +Davida: 0, 3, 3 +Davina: 0, 3, 3 +Davis: 10, 0, 10 +Dawn: 0, 202, 202 +Dawna: 0, 4, 4 +Dawne: 0, 2, 2 +Dayle: 0, 2, 2 +Dayna: 0, 8, 8 +Daysi: 0, 1, 1 +Deadra: 0, 1, 1 +Dean: 104, 0, 104 +Deana: 0, 16, 16 +Deandra: 0, 2, 2 +Deandre: 6, 0, 6 +Deandrea: 0, 1, 1 +Deane: 0, 2, 2 +Deangelo: 4, 0, 4 +Deann: 0, 8, 8 +Deanna: 0, 76, 76 +Deanne: 0, 11, 11 +Deb: 0, 3, 3 +Debbi: 0, 2, 2 +Debbie: 0, 157, 157 +Debbra: 0, 3, 3 +Debby: 0, 7, 7 +Debera: 0, 1, 1 +Debi: 0, 5, 5 +Debora: 0, 22, 22 +Deborah: 0, 494, 494 +Debra: 0, 408, 408 +Debrah: 0, 5, 5 +Debroah: 0, 2, 2 +Dede: 0, 1, 1 +Dedra: 0, 3, 3 +Dee: 5, 0, 5 +Deeann: 0, 2, 2 +Deeanna: 0, 1, 1 +Deedee: 0, 1, 1 +Deedra: 0, 1, 1 +Deena: 0, 10, 10 +Deetta: 0, 1, 1 +Deidra: 0, 8, 8 +Deidre: 0, 10, 10 +Deirdre: 0, 10, 10 +Deja: 0, 1, 1 +Del: 4, 0, 4 +Delaine: 0, 1, 1 +Delana: 0, 2, 2 +Delbert: 31, 0, 31 +Delcie: 0, 1, 1 +Delena: 0, 2, 2 +Delfina: 0, 5, 5 +Delia: 0, 29, 29 +Delicia: 0, 2, 2 +Delila: 0, 1, 1 +Delilah: 0, 7, 7 +Delinda: 0, 2, 2 +Delisa: 0, 2, 2 +Dell: 0, 2, 2 +Della: 0, 43, 43 +Delma: 0, 7, 7 +Delmar: 9, 0, 9 +Delmer: 5, 0, 5 +Delmy: 0, 1, 1 +Delois: 0, 6, 6 +Deloise: 0, 1, 1 +Delora: 0, 3, 3 +Deloras: 0, 1, 1 +Delores: 0, 95, 95 +Deloris: 0, 25, 25 +Delorse: 0, 1, 1 +Delpha: 0, 2, 2 +Delphia: 0, 3, 3 +Delphine: 0, 7, 7 +Delsie: 0, 1, 1 +Delta: 0, 2, 2 +Demarcus: 5, 0, 5 +Demetra: 0, 3, 3 +Demetria: 0, 7, 7 +Demetrice: 0, 2, 2 +Demetrius: 17, 0, 17 +Dena: 0, 22, 22 +Denae: 0, 1, 1 +Deneen: 0, 3, 3 +Denese: 0, 2, 2 +Denice: 0, 8, 8 +Denis: 14, 0, 14 +Denise: 0, 264, 264 +Denisha: 0, 2, 2 +Denisse: 0, 1, 1 +Denita: 0, 2, 2 +Denna: 0, 3, 3 +Dennis: 415, 0, 415 +Dennise: 0, 2, 2 +Denny: 10, 0, 10 +Denver: 9, 0, 9 +Denyse: 0, 1, 1 +Deon: 6, 0, 6 +Deonna: 0, 1, 1 +Derek: 112, 0, 112 +Derick: 11, 0, 11 +Derrick: 103, 0, 103 +Deshawn: 4, 0, 4 +Desirae: 0, 3, 3 +Desire: 0, 2, 2 +Desiree: 0, 35, 35 +Desmond: 14, 0, 14 +Despina: 0, 1, 1 +Dessie: 0, 8, 8 +Destiny: 0, 7, 7 +Detra: 0, 2, 2 +Devin: 28, 0, 28 +Devon: 14, 0, 14 +Devona: 0, 2, 2 +Devora: 0, 2, 2 +Devorah: 0, 2, 2 +Dewayne: 18, 0, 18 +Dewey: 23, 0, 23 +Dewitt: 6, 0, 6 +Dexter: 21, 0, 21 +Dia: 0, 1, 1 +Diamond: 0, 2, 2 +Dian: 0, 4, 4 +Diana: 0, 216, 216 +Diane: 0, 359, 359 +Diann: 0, 9, 9 +Dianna: 0, 36, 36 +Dianne: 0, 69, 69 +Dick: 9, 0, 9 +Diedra: 0, 2, 2 +Diedre: 0, 2, 2 +Diego: 11, 0, 11 +Dierdre: 0, 1, 1 +Digna: 0, 2, 2 +Dillon: 5, 0, 5 +Dimple: 0, 2, 2 +Dina: 0, 22, 22 +Dinah: 0, 7, 7 +Dino: 6, 0, 6 +Dinorah: 0, 1, 1 +Dion: 10, 0, 10 +Dione: 0, 2, 2 +Dionna: 0, 2, 2 +Dionne: 0, 9, 9 +Dirk: 8, 0, 8 +Divina: 0, 1, 1 +Dixie: 0, 28, 28 +Dodie: 0, 1, 1 +Dollie: 0, 11, 11 +Dolly: 0, 16, 16 +Dolores: 0, 129, 129 +Doloris: 0, 2, 2 +Domenic: 4, 0, 4 +Domenica: 0, 3, 3 +Dominga: 0, 6, 6 +Domingo: 19, 0, 19 +Dominic: 32, 0, 32 +Dominica: 0, 1, 1 +Dominick: 19, 0, 19 +Dominique: 8, 0, 8 +Dominque: 0, 2, 2 +Domitila: 0, 1, 1 +Domonique: 0, 2, 2 +Don: 145, 0, 145 +Dona: 0, 14, 14 +Donald: 931, 0, 931 +Donella: 0, 1, 1 +Donetta: 0, 2, 2 +Donette: 0, 1, 1 +Dong: 4, 0, 4 +Donita: 0, 4, 4 +Donn: 6, 0, 6 +Donna: 0, 583, 583 +Donnell: 10, 0, 10 +Donnetta: 0, 1, 1 +Donnette: 0, 1, 1 +Donnie: 41, 0, 41 +Donny: 9, 0, 9 +Donovan: 10, 0, 10 +Donte: 5, 0, 5 +Donya: 0, 2, 2 +Dora: 0, 84, 84 +Dorathy: 0, 2, 2 +Dorcas: 0, 5, 5 +Doreatha: 0, 1, 1 +Doreen: 0, 35, 35 +Dorene: 0, 5, 5 +Doretha: 0, 8, 8 +Dorethea: 0, 1, 1 +Doretta: 0, 1, 1 +Dori: 0, 4, 4 +Doria: 0, 1, 1 +Dorian: 6, 0, 6 +Dorie: 0, 2, 2 +Dorinda: 0, 4, 4 +Dorine: 0, 2, 2 +Doris: 0, 335, 335 +Dorla: 0, 1, 1 +Dorotha: 0, 3, 3 +Dorothea: 0, 21, 21 +Dorothy: 0, 727, 727 +Dorris: 0, 6, 6 +Dorsey: 4, 0, 4 +Dortha: 0, 5, 5 +Dorthea: 0, 3, 3 +Dorthey: 0, 1, 1 +Dorthy: 0, 25, 25 +Dot: 0, 2, 2 +Dottie: 0, 7, 7 +Dotty: 0, 1, 1 +Doug: 40, 0, 40 +Douglas: 367, 0, 367 +Douglass: 4, 0, 4 +Dovie: 0, 5, 5 +Doyle: 22, 0, 22 +Dreama: 0, 2, 2 +Drema: 0, 2, 2 +Drew: 24, 0, 24 +Drucilla: 0, 2, 2 +Drusilla: 0, 2, 2 +Duane: 77, 0, 77 +Dudley: 9, 0, 9 +Dulce: 0, 5, 5 +Dulcie: 0, 1, 1 +Duncan: 7, 0, 7 +Dung: 0, 2, 2 +Dusti: 0, 1, 1 +Dustin: 103, 0, 103 +Dusty: 7, 0, 7 +Dwain: 5, 0, 5 +Dwana: 0, 1, 1 +Dwayne: 59, 0, 59 +Dwight: 58, 0, 58 +Dyan: 0, 2, 2 +Dylan: 16, 0, 16 +Earl: 193, 0, 193 +Earle: 6, 0, 6 +Earlean: 0, 2, 2 +Earleen: 0, 2, 2 +Earlene: 0, 12, 12 +Earlie: 0, 1, 1 +Earline: 0, 14, 14 +Earnest: 31, 0, 31 +Earnestine: 0, 12, 12 +Eartha: 0, 3, 3 +Easter: 0, 5, 5 +Eboni: 0, 3, 3 +Ebonie: 0, 1, 1 +Ebony: 0, 27, 27 +Echo: 0, 2, 2 +Ed: 32, 0, 32 +Eda: 0, 3, 3 +Edda: 0, 2, 2 +Eddie: 144, 0, 144 +Eddy: 10, 0, 10 +Edelmira: 0, 3, 3 +Eden: 0, 3, 3 +Edgar: 80, 0, 80 +Edgardo: 6, 0, 6 +Edie: 0, 4, 4 +Edison: 4, 0, 4 +Edith: 0, 179, 179 +Edmond: 19, 0, 19 +Edmund: 30, 0, 30 +Edmundo: 4, 0, 4 +Edna: 0, 197, 197 +Edra: 0, 1, 1 +Edris: 0, 1, 1 +Eduardo: 47, 0, 47 +Edward: 779, 0, 779 +Edwardo: 8, 0, 8 +Edwin: 148, 0, 148 +Edwina: 0, 11, 11 +Edyth: 0, 1, 1 +Edythe: 0, 8, 8 +Effie: 0, 24, 24 +Efrain: 17, 0, 17 +Efren: 6, 0, 6 +Ehtel: 0, 1, 1 +Eileen: 0, 105, 105 +Eilene: 0, 1, 1 +Ela: 0, 1, 1 +Eladia: 0, 1, 1 +Elaina: 0, 4, 4 +Elaine: 0, 173, 173 +Elana: 0, 3, 3 +Elane: 0, 1, 1 +Elanor: 0, 2, 2 +Elayne: 0, 2, 2 +Elba: 0, 8, 8 +Elbert: 22, 0, 22 +Elda: 0, 8, 8 +Elden: 4, 0, 4 +Eldon: 17, 0, 17 +Eldora: 0, 3, 3 +Eldridge: 4, 0, 4 +Eleanor: 0, 150, 150 +Eleanora: 0, 3, 3 +Eleanore: 0, 7, 7 +Elease: 0, 2, 2 +Elena: 0, 37, 37 +Elene: 0, 1, 1 +Eleni: 0, 2, 2 +Elenor: 0, 4, 4 +Elenora: 0, 2, 2 +Elenore: 0, 1, 1 +Eleonor: 0, 1, 1 +Eleonora: 0, 1, 1 +Eleonore: 0, 1, 1 +Elfreda: 0, 1, 1 +Elfrieda: 0, 1, 1 +Elfriede: 0, 3, 3 +Eli: 16, 0, 16 +Elia: 0, 7, 7 +Eliana: 0, 2, 2 +Elias: 22, 0, 22 +Elicia: 0, 2, 2 +Elida: 0, 7, 7 +Elidia: 0, 1, 1 +Elijah: 19, 0, 19 +Elin: 0, 1, 1 +Elina: 0, 1, 1 +Elinor: 0, 12, 12 +Elinore: 0, 1, 1 +Elisa: 0, 27, 27 +Elisabeth: 0, 23, 23 +Elise: 0, 19, 19 +Eliseo: 6, 0, 6 +Elisha: 4, 0, 4 +Elissa: 0, 6, 6 +Eliz: 0, 1, 1 +Eliza: 0, 15, 15 +Elizabet: 0, 1, 1 +Elizabeth: 0, 937, 937 +Elizbeth: 0, 2, 2 +Elizebeth: 0, 4, 4 +Elke: 0, 2, 2 +Ella: 0, 101, 101 +Ellamae: 0, 1, 1 +Ellan: 0, 1, 1 +Ellen: 0, 173, 173 +Ellena: 0, 1, 1 +Elli: 0, 1, 1 +Ellie: 0, 6, 6 +Elliot: 14, 0, 14 +Elliott: 14, 0, 14 +Ellis: 24, 0, 24 +Ellsworth: 4, 0, 4 +Elly: 0, 2, 2 +Ellyn: 0, 2, 2 +Elma: 0, 17, 17 +Elmer: 74, 0, 74 +Elmira: 0, 3, 3 +Elmo: 6, 0, 6 +Elna: 0, 3, 3 +Elnora: 0, 14, 14 +Elodia: 0, 2, 2 +Elois: 0, 2, 2 +Eloisa: 0, 7, 7 +Eloise: 0, 32, 32 +Elouise: 0, 6, 6 +Eloy: 6, 0, 6 +Elroy: 4, 0, 4 +Elsa: 0, 28, 28 +Else: 0, 2, 2 +Elsie: 0, 110, 110 +Elsy: 0, 1, 1 +Elton: 16, 0, 16 +Elva: 0, 24, 24 +Elvera: 0, 3, 3 +Elvia: 0, 10, 10 +Elvie: 0, 2, 2 +Elvin: 13, 0, 13 +Elvina: 0, 2, 2 +Elvira: 0, 29, 29 +Elvis: 10, 0, 10 +Elwanda: 0, 1, 1 +Elwood: 13, 0, 13 +Elyse: 0, 6, 6 +Elza: 0, 1, 1 +Ema: 0, 2, 2 +Emanuel: 19, 0, 19 +Emelda: 0, 2, 2 +Emelia: 0, 3, 3 +Emelina: 0, 1, 1 +Emeline: 0, 1, 1 +Emely: 0, 1, 1 +Emerald: 0, 1, 1 +Emerita: 0, 1, 1 +Emerson: 8, 0, 8 +Emery: 10, 0, 10 +Emiko: 0, 2, 2 +Emil: 19, 0, 19 +Emile: 6, 0, 6 +Emilee: 0, 2, 2 +Emilia: 0, 13, 13 +Emilie: 0, 8, 8 +Emilio: 19, 0, 19 +Emily: 0, 208, 208 +Emma: 0, 165, 165 +Emmaline: 0, 1, 1 +Emmanuel: 18, 0, 18 +Emmett: 19, 0, 19 +Emmie: 0, 2, 2 +Emmitt: 4, 0, 4 +Emmy: 0, 2, 2 +Emogene: 0, 3, 3 +Emory: 8, 0, 8 +Ena: 0, 4, 4 +Enda: 0, 1, 1 +Enedina: 0, 3, 3 +Eneida: 0, 2, 2 +Enid: 0, 9, 9 +Enoch: 5, 0, 5 +Enola: 0, 2, 2 +Enrique: 46, 0, 46 +Enriqueta: 0, 4, 4 +Epifania: 0, 2, 2 +Era: 0, 4, 4 +Erasmo: 4, 0, 4 +Eric: 544, 0, 544 +Erica: 0, 130, 130 +Erich: 7, 0, 7 +Erick: 23, 0, 23 +Ericka: 0, 14, 14 +Erik: 68, 0, 68 +Erika: 0, 61, 61 +Erin: 7, 0, 7 +Erinn: 0, 2, 2 +Erlene: 0, 2, 2 +Erlinda: 0, 6, 6 +Erline: 0, 2, 2 +Erma: 0, 41, 41 +Ermelinda: 0, 2, 2 +Erminia: 0, 2, 2 +Erna: 0, 9, 9 +Ernest: 215, 0, 215 +Ernestina: 0, 7, 7 +Ernestine: 0, 38, 38 +Ernesto: 37, 0, 37 +Ernie: 15, 0, 15 +Errol: 8, 0, 8 +Ervin: 23, 0, 23 +Erwin: 15, 0, 15 +Eryn: 0, 1, 1 +Esmeralda: 0, 13, 13 +Esperanza: 0, 21, 21 +Essie: 0, 29, 29 +Esta: 0, 2, 2 +Esteban: 13, 0, 13 +Estefana: 0, 1, 1 +Estela: 0, 14, 14 +Estell: 0, 2, 2 +Estella: 0, 24, 24 +Estelle: 0, 36, 36 +Ester: 0, 19, 19 +Esther: 0, 166, 166 +Estrella: 0, 3, 3 +Etha: 0, 1, 1 +Ethan: 17, 0, 17 +Ethel: 0, 174, 174 +Ethelene: 0, 2, 2 +Ethelyn: 0, 3, 3 +Ethyl: 0, 2, 2 +Etsuko: 0, 1, 1 +Etta: 0, 24, 24 +Ettie: 0, 1, 1 +Eufemia: 0, 1, 1 +Eugena: 0, 1, 1 +Eugene: 230, 0, 230 +Eugenia: 0, 23, 23 +Eugenie: 0, 3, 3 +Eugenio: 7, 0, 7 +Eula: 0, 33, 33 +Eulah: 0, 1, 1 +Eulalia: 0, 5, 5 +Eun: 0, 3, 3 +Euna: 0, 1, 1 +Eunice: 0, 54, 54 +Eura: 0, 1, 1 +Eusebia: 0, 1, 1 +Eusebio: 4, 0, 4 +Eustolia: 0, 1, 1 +Eva: 0, 159, 159 +Evalyn: 0, 3, 3 +Evan: 42, 0, 42 +Evangelina: 0, 9, 9 +Evangeline: 0, 11, 11 +Eve: 0, 13, 13 +Evelia: 0, 3, 3 +Evelin: 0, 2, 2 +Evelina: 0, 2, 2 +Eveline: 0, 2, 2 +Evelyn: 0, 322, 322 +Evelyne: 0, 2, 2 +Evelynn: 0, 1, 1 +Everett: 57, 0, 57 +Everette: 6, 0, 6 +Evette: 0, 6, 6 +Evia: 0, 1, 1 +Evie: 0, 4, 4 +Evita: 0, 1, 1 +Evon: 0, 3, 3 +Evonne: 0, 4, 4 +Ewa: 0, 1, 1 +Exie: 0, 2, 2 +Ezekiel: 4, 0, 4 +Ezequiel: 4, 0, 4 +Ezra: 7, 0, 7 +Fabian: 12, 0, 12 +Fabiola: 0, 5, 5 +Fae: 0, 2, 2 +Fairy: 0, 1, 1 +Faith: 0, 28, 28 +Fallon: 0, 3, 3 +Fannie: 0, 50, 50 +Fanny: 0, 9, 9 +Farah: 0, 2, 2 +Farrah: 0, 4, 4 +Fatima: 0, 7, 7 +Fatimah: 0, 1, 1 +Faustina: 0, 2, 2 +Faustino: 6, 0, 6 +Fausto: 4, 0, 4 +Faviola: 0, 1, 1 +Fawn: 0, 4, 4 +Fay: 0, 21, 21 +Faye: 0, 58, 58 +Fe: 0, 2, 2 +Federico: 10, 0, 10 +Felecia: 0, 8, 8 +Felica: 0, 3, 3 +Felice: 0, 3, 3 +Felicia: 0, 68, 68 +Felicidad: 0, 1, 1 +Felicita: 0, 6, 6 +Felicitas: 0, 4, 4 +Felipa: 0, 4, 4 +Felipe: 32, 0, 32 +Felisa: 0, 3, 3 +Felisha: 0, 4, 4 +Felix: 58, 0, 58 +Felton: 4, 0, 4 +Ferdinand: 7, 0, 7 +Fermin: 5, 0, 5 +Fermina: 0, 1, 1 +Fern: 0, 24, 24 +Fernanda: 0, 2, 2 +Fernande: 0, 1, 1 +Fernando: 65, 0, 65 +Ferne: 0, 3, 3 +Fidel: 12, 0, 12 +Fidela: 0, 1, 1 +Fidelia: 0, 1, 1 +Filiberto: 4, 0, 4 +Filomena: 0, 6, 6 +Fiona: 0, 3, 3 +Flavia: 0, 2, 2 +Fleta: 0, 1, 1 +Fletcher: 8, 0, 8 +Flo: 0, 2, 2 +Flor: 0, 6, 6 +Flora: 0, 49, 49 +Florance: 0, 2, 2 +Florence: 0, 200, 200 +Florencia: 0, 2, 2 +Florencio: 5, 0, 5 +Florene: 0, 3, 3 +Florentina: 0, 2, 2 +Florentino: 4, 0, 4 +Floretta: 0, 2, 2 +Floria: 0, 1, 1 +Florida: 0, 4, 4 +Florinda: 0, 2, 2 +Florine: 0, 9, 9 +Florrie: 0, 1, 1 +Flossie: 0, 14, 14 +Floy: 0, 5, 5 +Floyd: 107, 0, 107 +Fonda: 0, 3, 3 +Forest: 7, 0, 7 +Forrest: 27, 0, 27 +Foster: 6, 0, 6 +Fran: 0, 11, 11 +France: 0, 2, 2 +Francene: 0, 1, 1 +Frances: 5, 0, 5 +Francesca: 0, 9, 9 +Francesco: 4, 0, 4 +Franchesca: 0, 1, 1 +Francie: 0, 2, 2 +Francina: 0, 2, 2 +Francine: 0, 25, 25 +Francis: 160, 0, 160 +Francisca: 0, 24, 24 +Francisco: 124, 0, 124 +Francoise: 0, 2, 2 +Frank: 581, 0, 581 +Frankie: 23, 0, 23 +Franklin: 77, 0, 77 +Franklyn: 4, 0, 4 +Fransisca: 0, 1, 1 +Fred: 251, 0, 251 +Freda: 0, 34, 34 +Fredda: 0, 1, 1 +Freddie: 46, 0, 46 +Freddy: 16, 0, 16 +Frederic: 9, 0, 9 +Frederica: 0, 2, 2 +Frederick: 154, 0, 154 +Fredericka: 0, 2, 2 +Fredia: 0, 2, 2 +Fredric: 5, 0, 5 +Fredrick: 43, 0, 43 +Fredricka: 0, 1, 1 +Freeda: 0, 2, 2 +Freeman: 7, 0, 7 +Freida: 0, 9, 9 +Frida: 0, 1, 1 +Frieda: 0, 17, 17 +Fritz: 6, 0, 6 +Fumiko: 0, 2, 2 +Gabriel: 73, 0, 73 +Gabriela: 0, 18, 18 +Gabriele: 0, 2, 2 +Gabriella: 0, 5, 5 +Gabrielle: 0, 17, 17 +Gail: 6, 0, 6 +Gala: 0, 1, 1 +Gale: 8, 0, 8 +Galen: 9, 0, 9 +Galina: 0, 1, 1 +Garfield: 5, 0, 5 +Garland: 17, 0, 17 +Garnet: 0, 4, 4 +Garnett: 0, 2, 2 +Garret: 4, 0, 4 +Garrett: 29, 0, 29 +Garry: 32, 0, 32 +Garth: 7, 0, 7 +Gary: 650, 0, 650 +Gaston: 4, 0, 4 +Gavin: 10, 0, 10 +Gay: 0, 12, 12 +Gaye: 0, 5, 5 +Gayla: 0, 8, 8 +Gayle: 4, 0, 4 +Gaylene: 0, 2, 2 +Gaylord: 4, 0, 4 +Gaynell: 0, 3, 3 +Gaynelle: 0, 1, 1 +Gearldine: 0, 2, 2 +Gema: 0, 1, 1 +Gemma: 0, 3, 3 +Gena: 0, 10, 10 +Genaro: 8, 0, 8 +Gene: 87, 0, 87 +Genesis: 0, 1, 1 +Geneva: 0, 59, 59 +Genevie: 0, 1, 1 +Genevieve: 0, 51, 51 +Genevive: 0, 1, 1 +Genia: 0, 2, 2 +Genie: 0, 2, 2 +Genna: 0, 1, 1 +Gennie: 0, 2, 2 +Genny: 0, 1, 1 +Genoveva: 0, 5, 5 +Geoffrey: 32, 0, 32 +Georgann: 0, 1, 1 +George: 927, 0, 927 +Georgeann: 0, 1, 1 +Georgeanna: 0, 1, 1 +Georgene: 0, 2, 2 +Georgetta: 0, 3, 3 +Georgette: 0, 11, 11 +Georgia: 0, 91, 91 +Georgiana: 0, 5, 5 +Georgiann: 0, 1, 1 +Georgianna: 0, 5, 5 +Georgianne: 0, 1, 1 +Georgie: 0, 4, 4 +Georgina: 0, 15, 15 +Georgine: 0, 2, 2 +Gerald: 309, 0, 309 +Geraldine: 0, 141, 141 +Geraldo: 5, 0, 5 +Geralyn: 0, 3, 3 +Gerard: 40, 0, 40 +Gerardo: 32, 0, 32 +Gerda: 0, 4, 4 +Geri: 0, 8, 8 +Germaine: 0, 8, 8 +German: 8, 0, 8 +Gerri: 0, 5, 5 +Gerry: 11, 0, 11 +Gertha: 0, 2, 2 +Gertie: 0, 5, 5 +Gertrud: 0, 2, 2 +Gertrude: 0, 103, 103 +Gertrudis: 0, 1, 1 +Gertude: 0, 1, 1 +Ghislaine: 0, 1, 1 +Gia: 0, 2, 2 +Gianna: 0, 1, 1 +Gidget: 0, 1, 1 +Gigi: 0, 2, 2 +Gil: 6, 0, 6 +Gilbert: 89, 0, 89 +Gilberte: 0, 1, 1 +Gilberto: 24, 0, 24 +Gilda: 0, 9, 9 +Gillian: 0, 6, 6 +Gilma: 0, 1, 1 +Gina: 0, 99, 99 +Ginette: 0, 1, 1 +Ginger: 0, 34, 34 +Ginny: 0, 5, 5 +Gino: 6, 0, 6 +Giovanna: 0, 3, 3 +Giovanni: 7, 0, 7 +Gisela: 0, 6, 6 +Gisele: 0, 3, 3 +Giselle: 0, 4, 4 +Gita: 0, 1, 1 +Giuseppe: 5, 0, 5 +Giuseppina: 0, 1, 1 +Gladis: 0, 3, 3 +Glady: 0, 2, 2 +Gladys: 0, 205, 205 +Glayds: 0, 1, 1 +Glen: 94, 0, 94 +Glenda: 0, 88, 88 +Glendora: 0, 2, 2 +Glenn: 167, 0, 167 +Glenna: 0, 18, 18 +Glennie: 0, 2, 2 +Glennis: 0, 2, 2 +Glinda: 0, 2, 2 +Gloria: 0, 335, 335 +Glory: 0, 3, 3 +Glynda: 0, 2, 2 +Glynis: 0, 1, 1 +Golda: 0, 3, 3 +Golden: 0, 1, 1 +Goldie: 0, 23, 23 +Gonzalo: 11, 0, 11 +Gordon: 104, 0, 104 +Grace: 0, 189, 189 +Gracia: 0, 2, 2 +Gracie: 0, 18, 18 +Graciela: 0, 19, 19 +Grady: 21, 0, 21 +Graham: 12, 0, 12 +Graig: 4, 0, 4 +Grant: 36, 0, 36 +Granville: 4, 0, 4 +Grayce: 0, 1, 1 +Grazyna: 0, 1, 1 +Greg: 104, 0, 104 +Gregg: 29, 0, 29 +Gregoria: 0, 5, 5 +Gregorio: 14, 0, 14 +Gregory: 441, 0, 441 +Greta: 0, 14, 14 +Gretchen: 0, 32, 32 +Gretta: 0, 3, 3 +Gricelda: 0, 1, 1 +Grisel: 0, 1, 1 +Griselda: 0, 7, 7 +Grover: 16, 0, 16 +Guadalupe: 26, 0, 26 +Gudrun: 0, 2, 2 +Guillermina: 0, 7, 7 +Guillermo: 31, 0, 31 +Gus: 11, 0, 11 +Gussie: 0, 8, 8 +Gustavo: 25, 0, 25 +Guy: 60, 0, 60 +Gwen: 0, 31, 31 +Gwenda: 0, 2, 2 +Gwendolyn: 0, 74, 74 +Gwenn: 0, 2, 2 +Gwyn: 0, 2, 2 +Gwyneth: 0, 1, 1 +Ha: 0, 2, 2 +Hae: 0, 2, 2 +Hai: 4, 0, 4 +Hailey: 0, 4, 4 +Hal: 13, 0, 13 +Haley: 0, 14, 14 +Halina: 0, 2, 2 +Halley: 0, 1, 1 +Hallie: 0, 9, 9 +Han: 0, 1, 1 +Hana: 0, 2, 2 +Hang: 0, 1, 1 +Hanh: 0, 2, 2 +Hank: 5, 0, 5 +Hanna: 0, 7, 7 +Hannah: 0, 45, 45 +Hannelore: 0, 2, 2 +Hans: 15, 0, 15 +Harlan: 14, 0, 14 +Harland: 4, 0, 4 +Harley: 17, 0, 17 +Harmony: 0, 2, 2 +Harold: 371, 0, 371 +Harriet: 0, 56, 56 +Harriett: 0, 13, 13 +Harriette: 0, 6, 6 +Harris: 9, 0, 9 +Harrison: 14, 0, 14 +Harry: 251, 0, 251 +Harvey: 72, 0, 72 +Hassan: 5, 0, 5 +Hassie: 0, 1, 1 +Hattie: 0, 56, 56 +Haydee: 0, 6, 6 +Hayden: 4, 0, 4 +Hayley: 0, 5, 5 +Haywood: 4, 0, 4 +Hazel: 0, 161, 161 +Heath: 17, 0, 17 +Heather: 0, 337, 337 +Hector: 94, 0, 94 +Hedwig: 0, 3, 3 +Hedy: 0, 2, 2 +Hee: 0, 2, 2 +Heide: 0, 2, 2 +Heidi: 0, 88, 88 +Heidy: 0, 1, 1 +Heike: 0, 1, 1 +Helaine: 0, 1, 1 +Helen: 0, 663, 663 +Helena: 0, 21, 21 +Helene: 0, 24, 24 +Helga: 0, 9, 9 +Hellen: 0, 5, 5 +Henrietta: 0, 31, 31 +Henriette: 0, 2, 2 +Henry: 365, 0, 365 +Herb: 4, 0, 4 +Herbert: 155, 0, 155 +Heriberto: 10, 0, 10 +Herlinda: 0, 5, 5 +Herma: 0, 1, 1 +Herman: 97, 0, 97 +Hermelinda: 0, 4, 4 +Hermila: 0, 1, 1 +Hermina: 0, 2, 2 +Hermine: 0, 2, 2 +Herminia: 0, 8, 8 +Herschel: 7, 0, 7 +Hershel: 6, 0, 6 +Herta: 0, 1, 1 +Hertha: 0, 1, 1 +Hester: 0, 9, 9 +Hettie: 0, 3, 3 +Hiedi: 0, 1, 1 +Hien: 0, 1, 1 +Hilaria: 0, 2, 2 +Hilario: 5, 0, 5 +Hilary: 0, 14, 14 +Hilda: 0, 75, 75 +Hilde: 0, 2, 2 +Hildegard: 0, 5, 5 +Hildegarde: 0, 2, 2 +Hildred: 0, 2, 2 +Hillary: 0, 13, 13 +Hilma: 0, 2, 2 +Hilton: 5, 0, 5 +Hipolito: 5, 0, 5 +Hiram: 10, 0, 10 +Hiroko: 0, 2, 2 +Hisako: 0, 1, 1 +Hoa: 0, 3, 3 +Hobert: 4, 0, 4 +Holley: 0, 2, 2 +Holli: 0, 4, 4 +Hollie: 0, 9, 9 +Hollis: 9, 0, 9 +Holly: 0, 117, 117 +Homer: 40, 0, 40 +Honey: 0, 2, 2 +Hong: 4, 0, 4 +Hope: 0, 34, 34 +Horace: 36, 0, 36 +Horacio: 5, 0, 5 +Hortencia: 0, 7, 7 +Hortense: 0, 4, 4 +Hortensia: 0, 3, 3 +Hosea: 4, 0, 4 +Houston: 8, 0, 8 +Howard: 230, 0, 230 +Hoyt: 4, 0, 4 +Hsiu: 0, 1, 1 +Hubert: 39, 0, 39 +Hue: 0, 1, 1 +Huey: 5, 0, 5 +Hugh: 60, 0, 60 +Hugo: 23, 0, 23 +Hui: 0, 3, 3 +Hulda: 0, 3, 3 +Humberto: 18, 0, 18 +Hung: 8, 0, 8 +Hunter: 9, 0, 9 +Huong: 0, 3, 3 +Hwa: 0, 1, 1 +Hyacinth: 0, 2, 2 +Hye: 0, 2, 2 +Hyman: 4, 0, 4 +Hyo: 0, 1, 1 +Hyon: 0, 2, 2 +Hyun: 0, 2, 2 +Ian: 56, 0, 56 +Ida: 0, 118, 118 +Idalia: 0, 3, 3 +Idell: 0, 2, 2 +Idella: 0, 5, 5 +Iesha: 0, 2, 2 +Ignacia: 0, 2, 2 +Ignacio: 23, 0, 23 +Ike: 4, 0, 4 +Ila: 0, 14, 14 +Ilana: 0, 2, 2 +Ilda: 0, 2, 2 +Ileana: 0, 4, 4 +Ileen: 0, 1, 1 +Ilene: 0, 11, 11 +Iliana: 0, 3, 3 +Illa: 0, 1, 1 +Ilona: 0, 3, 3 +Ilse: 0, 4, 4 +Iluminada: 0, 2, 2 +Ima: 0, 5, 5 +Imelda: 0, 10, 10 +Imogene: 0, 19, 19 +In: 0, 3, 3 +Ina: 0, 23, 23 +India: 0, 6, 6 +Indira: 0, 2, 2 +Inell: 0, 2, 2 +Ines: 0, 9, 9 +Inez: 0, 53, 53 +Inga: 0, 3, 3 +Inge: 0, 4, 4 +Ingeborg: 0, 3, 3 +Inger: 0, 2, 2 +Ingrid: 0, 23, 23 +Inocencia: 0, 2, 2 +Iola: 0, 7, 7 +Iona: 0, 7, 7 +Ione: 0, 6, 6 +Ira: 35, 0, 35 +Iraida: 0, 1, 1 +Irena: 0, 3, 3 +Irene: 0, 252, 252 +Irina: 0, 2, 2 +Iris: 0, 55, 55 +Irish: 0, 2, 2 +Irma: 0, 79, 79 +Irmgard: 0, 2, 2 +Irvin: 20, 0, 20 +Irving: 26, 0, 26 +Irwin: 9, 0, 9 +Isa: 0, 2, 2 +Isaac: 51, 0, 51 +Isabel: 0, 57, 57 +Isabell: 0, 6, 6 +Isabella: 0, 8, 8 +Isabelle: 0, 19, 19 +Isadora: 0, 1, 1 +Isaiah: 11, 0, 11 +Isaias: 5, 0, 5 +Isaura: 0, 2, 2 +Isela: 0, 3, 3 +Isiah: 6, 0, 6 +Isidra: 0, 2, 2 +Isidro: 9, 0, 9 +Isis: 0, 2, 2 +Ismael: 24, 0, 24 +Isobel: 0, 1, 1 +Israel: 28, 0, 28 +Isreal: 4, 0, 4 +Issac: 10, 0, 10 +Iva: 0, 23, 23 +Ivan: 53, 0, 53 +Ivana: 0, 2, 2 +Ivelisse: 0, 1, 1 +Ivette: 0, 7, 7 +Ivey: 0, 1, 1 +Ivonne: 0, 5, 5 +Ivory: 6, 0, 6 +Ivy: 0, 16, 16 +Izetta: 0, 1, 1 +Izola: 0, 1, 1 +Ja: 0, 1, 1 +Jacalyn: 0, 3, 3 +Jacelyn: 0, 1, 1 +Jacinda: 0, 1, 1 +Jacinta: 0, 3, 3 +Jacinto: 4, 0, 4 +Jack: 315, 0, 315 +Jackeline: 0, 3, 3 +Jackelyn: 0, 1, 1 +Jacki: 0, 2, 2 +Jackie: 43, 0, 43 +Jacklyn: 0, 9, 9 +Jackqueline: 0, 1, 1 +Jackson: 12, 0, 12 +Jaclyn: 0, 18, 18 +Jacob: 165, 0, 165 +Jacqualine: 0, 2, 2 +Jacque: 0, 5, 5 +Jacquelin: 0, 5, 5 +Jacqueline: 0, 228, 228 +Jacquelyn: 0, 41, 41 +Jacquelyne: 0, 2, 2 +Jacquelynn: 0, 2, 2 +Jacques: 8, 0, 8 +Jacquetta: 0, 1, 1 +Jacqui: 0, 1, 1 +Jacquie: 0, 1, 1 +Jacquiline: 0, 2, 2 +Jacquline: 0, 7, 7 +Jacqulyn: 0, 2, 2 +Jada: 0, 4, 4 +Jade: 0, 8, 8 +Jadwiga: 0, 2, 2 +Jae: 4, 0, 4 +Jaime: 55, 0, 55 +Jaimee: 0, 1, 1 +Jaimie: 0, 4, 4 +Jake: 25, 0, 25 +Jaleesa: 0, 2, 2 +Jalisa: 0, 1, 1 +Jama: 0, 1, 1 +Jamaal: 6, 0, 6 +Jamal: 14, 0, 14 +Jamar: 6, 0, 6 +Jame: 8, 0, 8 +Jamee: 0, 1, 1 +Jamel: 7, 0, 7 +James: 3318, 0, 3318 +Jamey: 5, 0, 5 +Jami: 0, 14, 14 +Jamie: 66, 0, 66 +Jamika: 0, 1, 1 +Jamila: 0, 4, 4 +Jamison: 5, 0, 5 +Jammie: 0, 3, 3 +Jan: 19, 0, 19 +Jana: 0, 31, 31 +Janae: 0, 3, 3 +Janay: 0, 2, 2 +Jane: 0, 250, 250 +Janean: 0, 1, 1 +Janee: 0, 1, 1 +Janeen: 0, 4, 4 +Janel: 0, 7, 7 +Janell: 0, 9, 9 +Janella: 0, 1, 1 +Janelle: 0, 24, 24 +Janene: 0, 3, 3 +Janessa: 0, 2, 2 +Janet: 0, 379, 379 +Janeth: 0, 2, 2 +Janett: 0, 2, 2 +Janetta: 0, 2, 2 +Janette: 0, 22, 22 +Janey: 0, 3, 3 +Jani: 0, 1, 1 +Janice: 0, 285, 285 +Janie: 0, 51, 51 +Janiece: 0, 1, 1 +Janina: 0, 4, 4 +Janine: 0, 20, 20 +Janis: 0, 34, 34 +Janise: 0, 1, 1 +Janita: 0, 2, 2 +Jann: 0, 2, 2 +Janna: 0, 10, 10 +Jannet: 0, 1, 1 +Jannette: 0, 5, 5 +Jannie: 0, 9, 9 +January: 0, 1, 1 +Janyce: 0, 1, 1 +Jaqueline: 0, 7, 7 +Jaquelyn: 0, 1, 1 +Jared: 71, 0, 71 +Jarod: 4, 0, 4 +Jarred: 6, 0, 6 +Jarrett: 7, 0, 7 +Jarrod: 14, 0, 14 +Jarvis: 10, 0, 10 +Jasmin: 0, 8, 8 +Jasmine: 0, 38, 38 +Jason: 660, 0, 660 +Jasper: 15, 0, 15 +Jaunita: 0, 3, 3 +Javier: 65, 0, 65 +Jay: 118, 0, 118 +Jaye: 0, 1, 1 +Jayme: 0, 6, 6 +Jaymie: 0, 1, 1 +Jayna: 0, 1, 1 +Jayne: 0, 18, 18 +Jayson: 10, 0, 10 +Jazmin: 0, 3, 3 +Jazmine: 0, 2, 2 +Jc: 4, 0, 4 +Jean: 35, 0, 35 +Jeana: 0, 5, 5 +Jeane: 0, 4, 4 +Jeanelle: 0, 1, 1 +Jeanene: 0, 2, 2 +Jeanett: 0, 1, 1 +Jeanetta: 0, 4, 4 +Jeanette: 0, 115, 115 +Jeanice: 0, 1, 1 +Jeanie: 0, 13, 13 +Jeanine: 0, 17, 17 +Jeanmarie: 0, 1, 1 +Jeanna: 0, 7, 7 +Jeanne: 0, 109, 109 +Jeannetta: 0, 1, 1 +Jeannette: 0, 46, 46 +Jeannie: 0, 28, 28 +Jeannine: 0, 16, 16 +Jed: 5, 0, 5 +Jeff: 166, 0, 166 +Jefferey: 5, 0, 5 +Jefferson: 10, 0, 10 +Jeffery: 166, 0, 166 +Jeffie: 0, 1, 1 +Jeffrey: 591, 0, 591 +Jeffry: 12, 0, 12 +Jen: 0, 2, 2 +Jena: 0, 7, 7 +Jenae: 0, 1, 1 +Jene: 0, 1, 1 +Jenee: 0, 1, 1 +Jenell: 0, 2, 2 +Jenelle: 0, 4, 4 +Jenette: 0, 2, 2 +Jeneva: 0, 1, 1 +Jeni: 0, 2, 2 +Jenice: 0, 2, 2 +Jenifer: 0, 23, 23 +Jeniffer: 0, 4, 4 +Jenine: 0, 2, 2 +Jenise: 0, 2, 2 +Jenna: 0, 30, 30 +Jennefer: 0, 2, 2 +Jennell: 0, 1, 1 +Jennette: 0, 4, 4 +Jenni: 0, 4, 4 +Jennie: 0, 73, 73 +Jennifer: 0, 932, 932 +Jenniffer: 0, 3, 3 +Jennine: 0, 1, 1 +Jenny: 0, 68, 68 +Jerald: 19, 0, 19 +Jeraldine: 0, 3, 3 +Jeramy: 4, 0, 4 +Jere: 4, 0, 4 +Jeremiah: 42, 0, 42 +Jeremy: 242, 0, 242 +Jeri: 0, 15, 15 +Jerica: 0, 1, 1 +Jerilyn: 0, 3, 3 +Jerlene: 0, 1, 1 +Jermaine: 28, 0, 28 +Jerold: 6, 0, 6 +Jerome: 108, 0, 108 +Jeromy: 4, 0, 4 +Jerrell: 4, 0, 4 +Jerri: 0, 12, 12 +Jerrica: 0, 1, 1 +Jerrie: 0, 4, 4 +Jerrod: 6, 0, 6 +Jerrold: 5, 0, 5 +Jerry: 432, 0, 432 +Jesenia: 0, 2, 2 +Jesica: 0, 5, 5 +Jess: 18, 0, 18 +Jesse: 209, 0, 209 +Jessenia: 0, 2, 2 +Jessi: 0, 2, 2 +Jessia: 0, 1, 1 +Jessica: 0, 490, 490 +Jessie: 65, 0, 65 +Jessika: 0, 1, 1 +Jestine: 0, 1, 1 +Jesus: 155, 0, 155 +Jesusa: 0, 2, 2 +Jesusita: 0, 1, 1 +Jetta: 0, 1, 1 +Jettie: 0, 3, 3 +Jewel: 4, 0, 4 +Jewell: 4, 0, 4 +Ji: 0, 1, 1 +Jill: 0, 142, 142 +Jillian: 0, 21, 21 +Jim: 118, 0, 118 +Jimmie: 58, 0, 58 +Jimmy: 191, 0, 191 +Jin: 0, 2, 2 +Jina: 0, 2, 2 +Jinny: 0, 1, 1 +Jo: 0, 83, 83 +Joan: 8, 0, 8 +Joana: 0, 3, 3 +Joane: 0, 1, 1 +Joanie: 0, 4, 4 +Joann: 0, 136, 136 +Joanna: 0, 55, 55 +Joanne: 0, 150, 150 +Joannie: 0, 1, 1 +Joaquin: 14, 0, 14 +Joaquina: 0, 1, 1 +Jocelyn: 0, 19, 19 +Jodee: 0, 2, 2 +Jodi: 0, 51, 51 +Jodie: 0, 17, 17 +Jody: 24, 0, 24 +Joe: 321, 0, 321 +Joeann: 0, 2, 2 +Joel: 152, 0, 152 +Joella: 0, 2, 2 +Joelle: 0, 5, 5 +Joellen: 0, 4, 4 +Joesph: 12, 0, 12 +Joetta: 0, 3, 3 +Joette: 0, 1, 1 +Joey: 43, 0, 43 +Johana: 0, 2, 2 +Johanna: 0, 28, 28 +Johanne: 0, 1, 1 +John: 3271, 0, 3271 +Johna: 0, 1, 1 +Johnathan: 34, 0, 34 +Johnathon: 9, 0, 9 +Johnetta: 0, 3, 3 +Johnette: 0, 1, 1 +Johnie: 7, 0, 7 +Johnna: 0, 5, 5 +Johnnie: 52, 0, 52 +Johnny: 195, 0, 195 +Johnsie: 0, 1, 1 +Johnson: 4, 0, 4 +Joi: 0, 3, 3 +Joie: 0, 1, 1 +Jolanda: 0, 2, 2 +Joleen: 0, 4, 4 +Jolene: 0, 19, 19 +Jolie: 0, 2, 2 +Joline: 0, 1, 1 +Jolyn: 0, 1, 1 +Jolynn: 0, 3, 3 +Jon: 115, 0, 115 +Jona: 0, 2, 2 +Jonah: 5, 0, 5 +Jonas: 7, 0, 7 +Jonathan: 313, 0, 313 +Jonathon: 32, 0, 32 +Jone: 0, 1, 1 +Jonell: 0, 2, 2 +Jonelle: 0, 2, 2 +Jong: 0, 1, 1 +Joni: 0, 19, 19 +Jonie: 0, 1, 1 +Jonna: 0, 3, 3 +Jonnie: 0, 4, 4 +Jordan: 56, 0, 56 +Jordon: 4, 0, 4 +Jorge: 104, 0, 104 +Jose: 613, 0, 613 +Josef: 6, 0, 6 +Josefa: 0, 10, 10 +Josefina: 0, 28, 28 +Josefine: 0, 1, 1 +Joselyn: 0, 2, 2 +Joseph: 1404, 0, 1404 +Josephina: 0, 4, 4 +Josephine: 0, 177, 177 +Josette: 0, 5, 5 +Josh: 23, 0, 23 +Joshua: 435, 0, 435 +Josiah: 6, 0, 6 +Josie: 0, 24, 24 +Joslyn: 0, 2, 2 +Jospeh: 4, 0, 4 +Josphine: 0, 1, 1 +Josue: 8, 0, 8 +Jovan: 0, 1, 1 +Jovita: 0, 5, 5 +Joy: 0, 91, 91 +Joya: 0, 1, 1 +Joyce: 0, 364, 364 +Joycelyn: 0, 4, 4 +Joye: 0, 2, 2 +Juan: 316, 0, 316 +Juana: 0, 46, 46 +Juanita: 0, 164, 164 +Jude: 4, 0, 4 +Judi: 0, 8, 8 +Judie: 0, 3, 3 +Judith: 0, 297, 297 +Judson: 6, 0, 6 +Judy: 0, 276, 276 +Jule: 0, 1, 1 +Julee: 0, 2, 2 +Julene: 0, 1, 1 +Jules: 6, 0, 6 +Juli: 0, 5, 5 +Julia: 0, 223, 223 +Julian: 52, 0, 52 +Juliana: 0, 11, 11 +Juliane: 0, 2, 2 +Juliann: 0, 3, 3 +Julianna: 0, 5, 5 +Julianne: 0, 12, 12 +Julie: 0, 348, 348 +Julieann: 0, 1, 1 +Julienne: 0, 2, 2 +Juliet: 0, 9, 9 +Julieta: 0, 3, 3 +Julietta: 0, 1, 1 +Juliette: 0, 10, 10 +Julio: 63, 0, 63 +Julissa: 0, 3, 3 +Julius: 42, 0, 42 +June: 0, 125, 125 +Jung: 0, 6, 6 +Junie: 0, 1, 1 +Junior: 16, 0, 16 +Junita: 0, 2, 2 +Junko: 0, 1, 1 +Justa: 0, 1, 1 +Justin: 311, 0, 311 +Justina: 0, 8, 8 +Justine: 0, 17, 17 +Jutta: 0, 2, 2 +Ka: 0, 1, 1 +Kacey: 0, 3, 3 +Kaci: 0, 3, 3 +Kacie: 0, 3, 3 +Kacy: 0, 2, 2 +Kai: 0, 1, 1 +Kaila: 0, 1, 1 +Kaitlin: 0, 9, 9 +Kaitlyn: 0, 8, 8 +Kala: 0, 4, 4 +Kaleigh: 0, 1, 1 +Kaley: 0, 2, 2 +Kali: 0, 2, 2 +Kallie: 0, 1, 1 +Kalyn: 0, 1, 1 +Kam: 0, 2, 2 +Kamala: 0, 1, 1 +Kami: 0, 4, 4 +Kamilah: 0, 1, 1 +Kandace: 0, 3, 3 +Kandi: 0, 3, 3 +Kandice: 0, 3, 3 +Kandis: 0, 1, 1 +Kandra: 0, 1, 1 +Kandy: 0, 3, 3 +Kanesha: 0, 1, 1 +Kanisha: 0, 1, 1 +Kara: 0, 41, 41 +Karan: 0, 3, 3 +Kareem: 6, 0, 6 +Kareen: 0, 1, 1 +Karen: 0, 667, 667 +Karena: 0, 1, 1 +Karey: 0, 2, 2 +Kari: 0, 36, 36 +Karie: 0, 3, 3 +Karima: 0, 1, 1 +Karin: 0, 24, 24 +Karina: 0, 13, 13 +Karine: 0, 2, 2 +Karisa: 0, 1, 1 +Karissa: 0, 4, 4 +Karl: 69, 0, 69 +Karla: 0, 44, 44 +Karleen: 0, 2, 2 +Karlene: 0, 3, 3 +Karly: 0, 1, 1 +Karlyn: 0, 1, 1 +Karma: 0, 2, 2 +Karmen: 0, 2, 2 +Karol: 0, 5, 5 +Karole: 0, 1, 1 +Karoline: 0, 1, 1 +Karolyn: 0, 4, 4 +Karon: 0, 5, 5 +Karren: 0, 2, 2 +Karri: 0, 3, 3 +Karrie: 0, 7, 7 +Karry: 0, 1, 1 +Kary: 0, 1, 1 +Karyl: 0, 2, 2 +Karyn: 0, 9, 9 +Kasandra: 0, 3, 3 +Kasey: 4, 0, 4 +Kasha: 0, 1, 1 +Kasi: 0, 1, 1 +Kasie: 0, 2, 2 +Kassandra: 0, 4, 4 +Kassie: 0, 2, 2 +Kate: 0, 29, 29 +Katelin: 0, 1, 1 +Katelyn: 0, 10, 10 +Katelynn: 0, 1, 1 +Katerine: 0, 1, 1 +Kathaleen: 0, 2, 2 +Katharina: 0, 2, 2 +Katharine: 0, 17, 17 +Katharyn: 0, 1, 1 +Kathe: 0, 2, 2 +Katheleen: 0, 1, 1 +Katherin: 0, 2, 2 +Katherina: 0, 1, 1 +Katherine: 0, 313, 313 +Kathern: 0, 2, 2 +Katheryn: 0, 9, 9 +Kathey: 0, 2, 2 +Kathi: 0, 8, 8 +Kathie: 0, 11, 11 +Kathleen: 0, 424, 424 +Kathlene: 0, 3, 3 +Kathline: 0, 1, 1 +Kathlyn: 0, 3, 3 +Kathrin: 0, 1, 1 +Kathrine: 0, 11, 11 +Kathryn: 0, 234, 234 +Kathryne: 0, 3, 3 +Kathy: 0, 272, 272 +Kathyrn: 0, 3, 3 +Kati: 0, 2, 2 +Katia: 0, 2, 2 +Katie: 0, 113, 113 +Katina: 0, 9, 9 +Katlyn: 0, 2, 2 +Katrice: 0, 2, 2 +Katrina: 0, 61, 61 +Kattie: 0, 2, 2 +Katy: 0, 12, 12 +Kay: 0, 71, 71 +Kayce: 0, 1, 1 +Kaycee: 0, 1, 1 +Kaye: 0, 11, 11 +Kayla: 0, 51, 51 +Kaylee: 0, 3, 3 +Kayleen: 0, 2, 2 +Kayleigh: 0, 2, 2 +Kaylene: 0, 2, 2 +Kazuko: 0, 2, 2 +Kecia: 0, 3, 3 +Keeley: 0, 1, 1 +Keely: 0, 4, 4 +Keena: 0, 2, 2 +Keenan: 5, 0, 5 +Keesha: 0, 2, 2 +Keiko: 0, 3, 3 +Keila: 0, 1, 1 +Keira: 0, 1, 1 +Keisha: 0, 19, 19 +Keith: 308, 0, 308 +Keitha: 0, 1, 1 +Keli: 0, 3, 3 +Kelle: 0, 2, 2 +Kellee: 0, 2, 2 +Kelley: 4, 0, 4 +Kelli: 0, 46, 46 +Kellie: 0, 29, 29 +Kelly: 63, 0, 63 +Kellye: 0, 2, 2 +Kelsey: 0, 24, 24 +Kelsi: 0, 1, 1 +Kelsie: 0, 3, 3 +Kelvin: 34, 0, 34 +Kemberly: 0, 1, 1 +Ken: 55, 0, 55 +Kena: 0, 2, 2 +Kenda: 0, 1, 1 +Kendal: 0, 1, 1 +Kendall: 16, 0, 16 +Kendra: 0, 38, 38 +Kendrick: 13, 0, 13 +Keneth: 4, 0, 4 +Kenia: 0, 1, 1 +Kenisha: 0, 2, 2 +Kenna: 0, 3, 3 +Kenneth: 826, 0, 826 +Kennith: 7, 0, 7 +Kenny: 39, 0, 39 +Kent: 48, 0, 48 +Kenton: 5, 0, 5 +Kenya: 0, 11, 11 +Kenyatta: 0, 3, 3 +Kenyetta: 0, 2, 2 +Kera: 0, 1, 1 +Keren: 0, 1, 1 +Keri: 0, 18, 18 +Kermit: 13, 0, 13 +Kerri: 0, 24, 24 +Kerrie: 0, 6, 6 +Kerry: 36, 0, 36 +Kerstin: 0, 1, 1 +Kesha: 0, 5, 5 +Keshia: 0, 5, 5 +Keturah: 0, 1, 1 +Keva: 0, 1, 1 +Keven: 5, 0, 5 +Kevin: 671, 0, 671 +Khadijah: 0, 1, 1 +Khalilah: 0, 1, 1 +Kia: 0, 5, 5 +Kiana: 0, 2, 2 +Kiara: 0, 3, 3 +Kiera: 0, 2, 2 +Kiersten: 0, 1, 1 +Kiesha: 0, 2, 2 +Kieth: 6, 0, 6 +Kiley: 0, 2, 2 +Kim: 28, 0, 28 +Kimber: 0, 2, 2 +Kimberely: 0, 3, 3 +Kimberlee: 0, 8, 8 +Kimberley: 0, 21, 21 +Kimberli: 0, 2, 2 +Kimberlie: 0, 2, 2 +Kimberly: 0, 504, 504 +Kimbery: 0, 2, 2 +Kimbra: 0, 1, 1 +Kimi: 0, 1, 1 +Kimiko: 0, 2, 2 +Kina: 0, 1, 1 +Kindra: 0, 2, 2 +King: 4, 0, 4 +Kip: 5, 0, 5 +Kira: 0, 6, 6 +Kirby: 9, 0, 9 +Kirk: 49, 0, 49 +Kirsten: 0, 18, 18 +Kirstie: 0, 1, 1 +Kirstin: 0, 3, 3 +Kisha: 0, 6, 6 +Kit: 0, 1, 1 +Kittie: 0, 1, 1 +Kitty: 0, 8, 8 +Kiyoko: 0, 1, 1 +Kizzie: 0, 1, 1 +Kizzy: 0, 3, 3 +Klara: 0, 1, 1 +Korey: 4, 0, 4 +Kori: 0, 4, 4 +Kortney: 0, 1, 1 +Kory: 5, 0, 5 +Kourtney: 0, 2, 2 +Kraig: 4, 0, 4 +Kris: 11, 0, 11 +Krishna: 0, 1, 1 +Krissy: 0, 1, 1 +Krista: 0, 40, 40 +Kristal: 0, 6, 6 +Kristan: 0, 3, 3 +Kristeen: 0, 1, 1 +Kristel: 0, 2, 2 +Kristen: 0, 111, 111 +Kristi: 0, 55, 55 +Kristian: 0, 3, 3 +Kristie: 0, 27, 27 +Kristin: 0, 99, 99 +Kristina: 0, 65, 65 +Kristine: 0, 51, 51 +Kristle: 0, 1, 1 +Kristofer: 4, 0, 4 +Kristopher: 25, 0, 25 +Kristy: 0, 48, 48 +Kristyn: 0, 3, 3 +Krysta: 0, 2, 2 +Krystal: 0, 37, 37 +Krysten: 0, 1, 1 +Krystin: 0, 1, 1 +Krystina: 0, 1, 1 +Krystle: 0, 7, 7 +Krystyna: 0, 3, 3 +Kum: 0, 3, 3 +Kurt: 62, 0, 62 +Kurtis: 9, 0, 9 +Kyla: 0, 5, 5 +Kyle: 160, 0, 160 +Kylee: 0, 3, 3 +Kylie: 0, 6, 6 +Kym: 0, 2, 2 +Kymberly: 0, 2, 2 +Kyoko: 0, 1, 1 +Kyong: 0, 4, 4 +Kyra: 0, 4, 4 +Kyung: 0, 4, 4 +Lacey: 0, 18, 18 +Lachelle: 0, 1, 1 +Laci: 0, 3, 3 +Lacie: 0, 4, 4 +Lacresha: 0, 1, 1 +Lacy: 4, 0, 4 +Ladawn: 0, 1, 1 +Ladonna: 0, 16, 16 +Lady: 0, 2, 2 +Lael: 0, 1, 1 +Lahoma: 0, 1, 1 +Lai: 0, 3, 3 +Laila: 0, 2, 2 +Laine: 0, 1, 1 +Lajuana: 0, 1, 1 +Lakeesha: 0, 1, 1 +Lakeisha: 0, 10, 10 +Lakendra: 0, 1, 1 +Lakenya: 0, 1, 1 +Lakesha: 0, 8, 8 +Lakeshia: 0, 4, 4 +Lakia: 0, 1, 1 +Lakiesha: 0, 1, 1 +Lakisha: 0, 11, 11 +Lakita: 0, 1, 1 +Lala: 0, 1, 1 +Lamar: 21, 0, 21 +Lamonica: 0, 1, 1 +Lamont: 17, 0, 17 +Lan: 0, 5, 5 +Lana: 0, 29, 29 +Lance: 63, 0, 63 +Landon: 8, 0, 8 +Lane: 9, 0, 9 +Lanell: 0, 2, 2 +Lanelle: 0, 1, 1 +Lanette: 0, 3, 3 +Lang: 0, 1, 1 +Lani: 0, 3, 3 +Lanie: 0, 1, 1 +Lanita: 0, 3, 3 +Lannie: 0, 1, 1 +Lanny: 6, 0, 6 +Lanora: 0, 1, 1 +Laquanda: 0, 1, 1 +Laquita: 0, 5, 5 +Lara: 0, 16, 16 +Larae: 0, 2, 2 +Laraine: 0, 2, 2 +Laree: 0, 1, 1 +Larhonda: 0, 2, 2 +Larisa: 0, 2, 2 +Larissa: 0, 6, 6 +Larita: 0, 1, 1 +Laronda: 0, 2, 2 +Larraine: 0, 1, 1 +Larry: 598, 0, 598 +Larue: 0, 3, 3 +Lasandra: 0, 2, 2 +Lashanda: 0, 4, 4 +Lashandra: 0, 1, 1 +Lashaun: 0, 1, 1 +Lashaunda: 0, 1, 1 +Lashawn: 0, 7, 7 +Lashawna: 0, 1, 1 +Lashawnda: 0, 1, 1 +Lashay: 0, 1, 1 +Lashell: 0, 1, 1 +Lashon: 0, 1, 1 +Lashonda: 0, 8, 8 +Lashunda: 0, 2, 2 +Lasonya: 0, 1, 1 +Latanya: 0, 8, 8 +Latarsha: 0, 1, 1 +Latasha: 0, 26, 26 +Latashia: 0, 2, 2 +Latesha: 0, 2, 2 +Latia: 0, 2, 2 +Laticia: 0, 2, 2 +Latina: 0, 1, 1 +Latisha: 0, 15, 15 +Latonia: 0, 5, 5 +Latonya: 0, 22, 22 +Latoria: 0, 2, 2 +Latosha: 0, 4, 4 +Latoya: 0, 43, 43 +Latoyia: 0, 1, 1 +Latrice: 0, 7, 7 +Latricia: 0, 5, 5 +Latrina: 0, 2, 2 +Latrisha: 0, 1, 1 +Launa: 0, 2, 2 +Laura: 0, 510, 510 +Lauralee: 0, 1, 1 +Lauran: 0, 1, 1 +Laure: 0, 2, 2 +Laureen: 0, 4, 4 +Laurel: 0, 24, 24 +Lauren: 4, 0, 4 +Laurena: 0, 1, 1 +Laurence: 24, 0, 24 +Laurene: 0, 3, 3 +Lauretta: 0, 6, 6 +Laurette: 0, 4, 4 +Lauri: 0, 9, 9 +Laurice: 0, 2, 2 +Laurie: 0, 114, 114 +Laurinda: 0, 1, 1 +Laurine: 0, 2, 2 +Lauryn: 0, 1, 1 +Lavada: 0, 3, 3 +Lavelle: 0, 1, 1 +Lavenia: 0, 1, 1 +Lavera: 0, 2, 2 +Lavern: 6, 0, 6 +Laverna: 0, 2, 2 +Laverne: 8, 0, 8 +Laveta: 0, 1, 1 +Lavette: 0, 1, 1 +Lavina: 0, 4, 4 +Lavinia: 0, 3, 3 +Lavon: 0, 4, 4 +Lavona: 0, 1, 1 +Lavonda: 0, 3, 3 +Lavone: 0, 1, 1 +Lavonia: 0, 2, 2 +Lavonna: 0, 1, 1 +Lavonne: 0, 11, 11 +Lawana: 0, 2, 2 +Lawanda: 0, 11, 11 +Lawanna: 0, 2, 2 +Lawerence: 5, 0, 5 +Lawrence: 282, 0, 282 +Layla: 0, 2, 2 +Layne: 0, 1, 1 +Lazaro: 7, 0, 7 +Le: 0, 2, 2 +Lea: 0, 17, 17 +Leah: 0, 72, 72 +Lean: 0, 1, 1 +Leana: 0, 2, 2 +Leandra: 0, 3, 3 +Leandro: 4, 0, 4 +Leann: 0, 15, 15 +Leanna: 0, 9, 9 +Leanne: 0, 16, 16 +Leanora: 0, 1, 1 +Leatha: 0, 3, 3 +Leatrice: 0, 4, 4 +Lecia: 0, 1, 1 +Leda: 0, 3, 3 +Lee: 162, 0, 162 +Leeann: 0, 8, 8 +Leeanna: 0, 2, 2 +Leeanne: 0, 2, 2 +Leena: 0, 1, 1 +Leesa: 0, 3, 3 +Leia: 0, 1, 1 +Leida: 0, 1, 1 +Leif: 4, 0, 4 +Leigh: 4, 0, 4 +Leigha: 0, 1, 1 +Leighann: 0, 2, 2 +Leila: 0, 15, 15 +Leilani: 0, 5, 5 +Leisa: 0, 4, 4 +Leisha: 0, 1, 1 +Lekisha: 0, 1, 1 +Lela: 0, 28, 28 +Lelah: 0, 1, 1 +Leland: 27, 0, 27 +Lelia: 0, 9, 9 +Lemuel: 6, 0, 6 +Len: 4, 0, 4 +Lena: 0, 77, 77 +Lenard: 5, 0, 5 +Lenita: 0, 1, 1 +Lenna: 0, 2, 2 +Lennie: 0, 3, 3 +Lenny: 5, 0, 5 +Lenora: 0, 24, 24 +Lenore: 0, 14, 14 +Leo: 106, 0, 106 +Leola: 0, 19, 19 +Leoma: 0, 1, 1 +Leon: 112, 0, 112 +Leona: 0, 69, 69 +Leonard: 186, 0, 186 +Leonarda: 0, 2, 2 +Leonardo: 15, 0, 15 +Leone: 0, 5, 5 +Leonel: 8, 0, 8 +Leonia: 0, 1, 1 +Leonida: 0, 1, 1 +Leonie: 0, 2, 2 +Leonila: 0, 2, 2 +Leonor: 0, 10, 10 +Leonora: 0, 5, 5 +Leonore: 0, 2, 2 +Leontine: 0, 1, 1 +Leopoldo: 6, 0, 6 +Leora: 0, 7, 7 +Leota: 0, 6, 6 +Lera: 0, 2, 2 +Leroy: 125, 0, 125 +Les: 6, 0, 6 +Lesa: 0, 8, 8 +Lesha: 0, 1, 1 +Lesia: 0, 2, 2 +Leslee: 0, 3, 3 +Lesley: 4, 0, 4 +Lesli: 0, 3, 3 +Leslie: 81, 0, 81 +Lessie: 0, 10, 10 +Lester: 91, 0, 91 +Leta: 0, 8, 8 +Letha: 0, 14, 14 +Leticia: 0, 40, 40 +Letisha: 0, 1, 1 +Letitia: 0, 8, 8 +Lettie: 0, 6, 6 +Letty: 0, 2, 2 +Levi: 25, 0, 25 +Lewis: 99, 0, 99 +Lexie: 0, 2, 2 +Lezlie: 0, 2, 2 +Li: 0, 3, 3 +Lia: 0, 5, 5 +Liana: 0, 3, 3 +Liane: 0, 3, 3 +Lianne: 0, 1, 1 +Libbie: 0, 1, 1 +Libby: 0, 8, 8 +Liberty: 0, 2, 2 +Librada: 0, 2, 2 +Lida: 0, 4, 4 +Lidia: 0, 14, 14 +Lien: 0, 2, 2 +Lieselotte: 0, 1, 1 +Ligia: 0, 2, 2 +Lila: 0, 29, 29 +Lili: 0, 2, 2 +Lilia: 0, 12, 12 +Lilian: 0, 11, 11 +Liliana: 0, 10, 10 +Lilla: 0, 2, 2 +Lilli: 0, 1, 1 +Lillia: 0, 1, 1 +Lilliam: 0, 1, 1 +Lillian: 0, 211, 211 +Lilliana: 0, 1, 1 +Lillie: 0, 90, 90 +Lilly: 0, 14, 14 +Lily: 0, 17, 17 +Lin: 0, 3, 3 +Lina: 0, 11, 11 +Lincoln: 8, 0, 8 +Linda: 0, 1035, 1035 +Lindsay: 4, 0, 4 +Lindsey: 7, 0, 7 +Lindsy: 0, 1, 1 +Lindy: 0, 5, 5 +Linette: 0, 3, 3 +Ling: 0, 2, 2 +Linh: 0, 3, 3 +Linn: 0, 1, 1 +Linnea: 0, 4, 4 +Linnie: 0, 4, 4 +Lino: 4, 0, 4 +Linsey: 0, 3, 3 +Linwood: 9, 0, 9 +Lionel: 24, 0, 24 +Lisa: 0, 704, 704 +Lisabeth: 0, 1, 1 +Lisandra: 0, 1, 1 +Lisbeth: 0, 2, 2 +Lise: 0, 4, 4 +Lisette: 0, 4, 4 +Lisha: 0, 2, 2 +Lissa: 0, 3, 3 +Lissette: 0, 4, 4 +Lita: 0, 3, 3 +Livia: 0, 2, 2 +Liz: 0, 9, 9 +Liza: 0, 12, 12 +Lizabeth: 0, 4, 4 +Lizbeth: 0, 4, 4 +Lizeth: 0, 1, 1 +Lizette: 0, 5, 5 +Lizzette: 0, 1, 1 +Lizzie: 0, 18, 18 +Lloyd: 112, 0, 112 +Loan: 0, 2, 2 +Logan: 17, 0, 17 +Loida: 0, 2, 2 +Lois: 0, 220, 220 +Loise: 0, 1, 1 +Lola: 0, 48, 48 +Lolita: 0, 9, 9 +Loma: 0, 2, 2 +Lon: 7, 0, 7 +Lona: 0, 7, 7 +Londa: 0, 1, 1 +Long: 4, 0, 4 +Loni: 0, 2, 2 +Lonna: 0, 2, 2 +Lonnie: 64, 0, 64 +Lonny: 4, 0, 4 +Lora: 0, 36, 36 +Loraine: 0, 13, 13 +Loralee: 0, 1, 1 +Lore: 0, 2, 2 +Lorean: 0, 1, 1 +Loree: 0, 2, 2 +Loreen: 0, 3, 3 +Lorelei: 0, 3, 3 +Loren: 32, 0, 32 +Lorena: 0, 29, 29 +Lorene: 0, 28, 28 +Lorenza: 0, 4, 4 +Lorenzo: 36, 0, 36 +Loreta: 0, 1, 1 +Loretta: 0, 115, 115 +Lorette: 0, 1, 1 +Lori: 0, 248, 248 +Loria: 0, 1, 1 +Loriann: 0, 2, 2 +Lorie: 0, 16, 16 +Lorilee: 0, 1, 1 +Lorina: 0, 1, 1 +Lorinda: 0, 3, 3 +Lorine: 0, 5, 5 +Loris: 0, 1, 1 +Lorita: 0, 1, 1 +Lorna: 0, 22, 22 +Lorraine: 0, 135, 135 +Lorretta: 0, 3, 3 +Lorri: 0, 6, 6 +Lorriane: 0, 1, 1 +Lorrie: 0, 12, 12 +Lorrine: 0, 1, 1 +Lory: 0, 2, 2 +Lottie: 0, 24, 24 +Lou: 5, 0, 5 +Louann: 0, 5, 5 +Louanne: 0, 2, 2 +Louella: 0, 8, 8 +Louetta: 0, 1, 1 +Louie: 18, 0, 18 +Louis: 243, 0, 243 +Louisa: 0, 12, 12 +Louise: 0, 229, 229 +Loura: 0, 1, 1 +Lourdes: 0, 24, 24 +Lourie: 0, 1, 1 +Louvenia: 0, 2, 2 +Love: 0, 1, 1 +Lovella: 0, 2, 2 +Lovetta: 0, 1, 1 +Lovie: 0, 4, 4 +Lowell: 29, 0, 29 +Loyce: 0, 2, 2 +Loyd: 11, 0, 11 +Lu: 0, 3, 3 +Luana: 0, 3, 3 +Luann: 0, 11, 11 +Luanna: 0, 1, 1 +Luanne: 0, 5, 5 +Luba: 0, 1, 1 +Lucas: 31, 0, 31 +Luci: 0, 1, 1 +Lucia: 0, 28, 28 +Luciana: 0, 3, 3 +Luciano: 7, 0, 7 +Lucie: 0, 4, 4 +Lucien: 7, 0, 7 +Lucienne: 0, 3, 3 +Lucila: 0, 5, 5 +Lucile: 0, 16, 16 +Lucilla: 0, 1, 1 +Lucille: 0, 153, 153 +Lucina: 0, 2, 2 +Lucinda: 0, 25, 25 +Lucio: 6, 0, 6 +Lucius: 4, 0, 4 +Lucrecia: 0, 2, 2 +Lucretia: 0, 7, 7 +Lucy: 0, 103, 103 +Ludie: 0, 2, 2 +Ludivina: 0, 1, 1 +Lue: 0, 3, 3 +Luella: 0, 17, 17 +Luetta: 0, 1, 1 +Luigi: 4, 0, 4 +Luis: 189, 0, 189 +Luisa: 0, 16, 16 +Luise: 0, 2, 2 +Luke: 40, 0, 40 +Lula: 0, 48, 48 +Lulu: 0, 4, 4 +Luna: 0, 2, 2 +Lupe: 5, 0, 5 +Lupita: 0, 3, 3 +Lura: 0, 6, 6 +Lurlene: 0, 1, 1 +Lurline: 0, 2, 2 +Luther: 43, 0, 43 +Luvenia: 0, 2, 2 +Luz: 0, 49, 49 +Lyda: 0, 3, 3 +Lydia: 0, 86, 86 +Lyla: 0, 3, 3 +Lyle: 38, 0, 38 +Lyman: 6, 0, 6 +Lyn: 0, 6, 6 +Lynda: 0, 53, 53 +Lyndia: 0, 1, 1 +Lyndon: 5, 0, 5 +Lyndsay: 0, 3, 3 +Lyndsey: 0, 4, 4 +Lynell: 0, 2, 2 +Lynelle: 0, 2, 2 +Lynetta: 0, 1, 1 +Lynette: 0, 33, 33 +Lynn: 38, 0, 38 +Lynna: 0, 1, 1 +Lynne: 0, 43, 43 +Lynnette: 0, 10, 10 +Lynsey: 0, 2, 2 +Lynwood: 4, 0, 4 +Ma: 0, 8, 8 +Mabel: 0, 78, 78 +Mabelle: 0, 1, 1 +Mable: 0, 38, 38 +Mac: 5, 0, 5 +Machelle: 0, 3, 3 +Macie: 0, 2, 2 +Mack: 25, 0, 25 +Mackenzie: 0, 4, 4 +Macy: 0, 2, 2 +Madalene: 0, 1, 1 +Madaline: 0, 2, 2 +Madalyn: 0, 2, 2 +Maddie: 0, 1, 1 +Madelaine: 0, 1, 1 +Madeleine: 0, 9, 9 +Madelene: 0, 1, 1 +Madeline: 0, 52, 52 +Madelyn: 0, 10, 10 +Madge: 0, 11, 11 +Madie: 0, 2, 2 +Madison: 0, 3, 3 +Madlyn: 0, 2, 2 +Madonna: 0, 6, 6 +Mae: 0, 63, 63 +Maegan: 0, 3, 3 +Mafalda: 0, 1, 1 +Magali: 0, 1, 1 +Magaly: 0, 2, 2 +Magan: 0, 2, 2 +Magaret: 0, 2, 2 +Magda: 0, 5, 5 +Magdalen: 0, 2, 2 +Magdalena: 0, 15, 15 +Magdalene: 0, 4, 4 +Magen: 0, 2, 2 +Maggie: 0, 51, 51 +Magnolia: 0, 4, 4 +Mahalia: 0, 1, 1 +Mai: 0, 9, 9 +Maia: 0, 2, 2 +Maida: 0, 2, 2 +Maile: 0, 1, 1 +Maira: 0, 3, 3 +Maire: 0, 1, 1 +Maisha: 0, 1, 1 +Maisie: 0, 1, 1 +Major: 7, 0, 7 +Majorie: 0, 5, 5 +Makeda: 0, 1, 1 +Malcolm: 34, 0, 34 +Malcom: 4, 0, 4 +Malena: 0, 1, 1 +Malia: 0, 3, 3 +Malik: 4, 0, 4 +Malika: 0, 2, 2 +Malinda: 0, 13, 13 +Malisa: 0, 2, 2 +Malissa: 0, 7, 7 +Malka: 0, 1, 1 +Mallie: 0, 1, 1 +Mallory: 0, 12, 12 +Malorie: 0, 1, 1 +Malvina: 0, 1, 1 +Mamie: 0, 48, 48 +Mammie: 0, 2, 2 +Man: 4, 0, 4 +Mana: 0, 2, 2 +Manda: 0, 2, 2 +Mandi: 0, 6, 6 +Mandie: 0, 1, 1 +Mandy: 0, 29, 29 +Manie: 0, 1, 1 +Manual: 4, 0, 4 +Manuel: 181, 0, 181 +Manuela: 0, 16, 16 +Many: 0, 1, 1 +Mao: 0, 1, 1 +Maple: 0, 2, 2 +Mara: 0, 9, 9 +Maragaret: 0, 1, 1 +Maragret: 0, 1, 1 +Maranda: 0, 4, 4 +Marc: 87, 0, 87 +Marcel: 12, 0, 12 +Marcela: 0, 6, 6 +Marcelene: 0, 1, 1 +Marcelina: 0, 4, 4 +Marceline: 0, 2, 2 +Marcelino: 9, 0, 9 +Marcell: 0, 1, 1 +Marcella: 0, 37, 37 +Marcelle: 0, 5, 5 +Marcellus: 4, 0, 4 +Marcelo: 5, 0, 5 +Marcene: 0, 1, 1 +Marchelle: 0, 1, 1 +Marci: 0, 10, 10 +Marcia: 0, 90, 90 +Marcie: 0, 12, 12 +Marco: 33, 0, 33 +Marcos: 25, 0, 25 +Marcus: 124, 0, 124 +Marcy: 0, 16, 16 +Mardell: 0, 1, 1 +Maren: 0, 2, 2 +Marg: 0, 1, 1 +Margaret: 0, 768, 768 +Margareta: 0, 2, 2 +Margarete: 0, 3, 3 +Margarett: 0, 2, 2 +Margaretta: 0, 2, 2 +Margarette: 0, 4, 4 +Margarita: 0, 59, 59 +Margarite: 0, 2, 2 +Margarito: 6, 0, 6 +Margart: 0, 2, 2 +Marge: 0, 6, 6 +Margene: 0, 1, 1 +Margeret: 0, 2, 2 +Margert: 0, 1, 1 +Margery: 0, 11, 11 +Marget: 0, 2, 2 +Margherita: 0, 1, 1 +Margie: 0, 72, 72 +Margit: 0, 2, 2 +Margo: 0, 16, 16 +Margorie: 0, 2, 2 +Margot: 0, 8, 8 +Margret: 0, 17, 17 +Margrett: 0, 1, 1 +Marguerita: 0, 2, 2 +Marguerite: 0, 56, 56 +Margurite: 0, 2, 2 +Margy: 0, 1, 1 +Marhta: 0, 1, 1 +Mari: 0, 11, 11 +Maria: 5, 0, 5 +Mariah: 0, 5, 5 +Mariam: 0, 5, 5 +Marian: 0, 86, 86 +Mariana: 0, 9, 9 +Marianela: 0, 1, 1 +Mariann: 0, 4, 4 +Marianna: 0, 7, 7 +Marianne: 0, 42, 42 +Mariano: 8, 0, 8 +Maribel: 0, 20, 20 +Maribeth: 0, 3, 3 +Marica: 0, 1, 1 +Maricela: 0, 11, 11 +Maricruz: 0, 1, 1 +Marie: 0, 379, 379 +Mariel: 0, 2, 2 +Mariela: 0, 3, 3 +Mariella: 0, 1, 1 +Marielle: 0, 1, 1 +Marietta: 0, 10, 10 +Mariette: 0, 1, 1 +Mariko: 0, 1, 1 +Marilee: 0, 4, 4 +Marilou: 0, 3, 3 +Marilu: 0, 2, 2 +Marilyn: 0, 241, 241 +Marilynn: 0, 7, 7 +Marin: 0, 1, 1 +Marina: 0, 27, 27 +Marinda: 0, 1, 1 +Marine: 0, 1, 1 +Mario: 125, 0, 125 +Marion: 48, 0, 48 +Maris: 0, 1, 1 +Marisa: 0, 18, 18 +Marisela: 0, 7, 7 +Marisha: 0, 1, 1 +Marisol: 0, 18, 18 +Marissa: 0, 24, 24 +Marita: 0, 4, 4 +Maritza: 0, 16, 16 +Marivel: 0, 1, 1 +Marjorie: 0, 173, 173 +Marjory: 0, 6, 6 +Mark: 938, 0, 938 +Marketta: 0, 1, 1 +Markita: 0, 2, 2 +Markus: 5, 0, 5 +Marla: 0, 26, 26 +Marlana: 0, 2, 2 +Marleen: 0, 2, 2 +Marlen: 0, 2, 2 +Marlena: 0, 5, 5 +Marlene: 0, 88, 88 +Marlin: 12, 0, 12 +Marline: 0, 1, 1 +Marlo: 0, 3, 3 +Marlon: 19, 0, 19 +Marlyn: 0, 5, 5 +Marlys: 0, 5, 5 +Marna: 0, 2, 2 +Marni: 0, 2, 2 +Marnie: 0, 4, 4 +Marquerite: 0, 4, 4 +Marquetta: 0, 2, 2 +Marquis: 6, 0, 6 +Marquita: 0, 9, 9 +Marquitta: 0, 1, 1 +Marry: 0, 3, 3 +Marsha: 0, 78, 78 +Marshall: 49, 0, 49 +Marta: 0, 28, 28 +Marth: 0, 1, 1 +Martha: 0, 412, 412 +Marti: 0, 4, 4 +Martin: 216, 0, 216 +Martina: 0, 16, 16 +Martine: 0, 3, 3 +Marty: 24, 0, 24 +Marva: 0, 10, 10 +Marvel: 0, 4, 4 +Marvella: 0, 1, 1 +Marvin: 171, 0, 171 +Marvis: 0, 1, 1 +Marx: 0, 1, 1 +Mary: 9, 0, 9 +Marya: 0, 2, 2 +Maryalice: 0, 2, 2 +Maryam: 0, 2, 2 +Maryann: 0, 50, 50 +Maryanna: 0, 1, 1 +Maryanne: 0, 11, 11 +Marybelle: 0, 1, 1 +Marybeth: 0, 7, 7 +Maryellen: 0, 9, 9 +Maryetta: 0, 1, 1 +Maryjane: 0, 7, 7 +Maryjo: 0, 3, 3 +Maryland: 0, 1, 1 +Marylee: 0, 3, 3 +Marylin: 0, 4, 4 +Maryln: 0, 1, 1 +Marylou: 0, 13, 13 +Marylouise: 0, 1, 1 +Marylyn: 0, 3, 3 +Marylynn: 0, 1, 1 +Maryrose: 0, 1, 1 +Masako: 0, 2, 2 +Mason: 12, 0, 12 +Matha: 0, 1, 1 +Mathew: 64, 0, 64 +Mathilda: 0, 3, 3 +Mathilde: 0, 2, 2 +Matilda: 0, 15, 15 +Matilde: 0, 6, 6 +Matt: 38, 0, 38 +Matthew: 657, 0, 657 +Mattie: 0, 81, 81 +Maud: 0, 5, 5 +Maude: 0, 23, 23 +Maudie: 0, 7, 7 +Maura: 0, 9, 9 +Maureen: 0, 92, 92 +Maurice: 97, 0, 97 +Mauricio: 10, 0, 10 +Maurine: 0, 7, 7 +Maurita: 0, 1, 1 +Mauro: 5, 0, 5 +Mavis: 0, 16, 16 +Max: 59, 0, 59 +Maxie: 0, 4, 4 +Maxima: 0, 1, 1 +Maximina: 0, 1, 1 +Maximo: 5, 0, 5 +Maxine: 0, 79, 79 +Maxwell: 10, 0, 10 +May: 0, 29, 29 +Maya: 0, 6, 6 +Maybell: 0, 2, 2 +Maybelle: 0, 4, 4 +Maye: 0, 2, 2 +Mayme: 0, 6, 6 +Maynard: 10, 0, 10 +Mayola: 0, 1, 1 +Mayra: 0, 18, 18 +Mazie: 0, 3, 3 +Mckenzie: 0, 2, 2 +Mckinley: 5, 0, 5 +Meagan: 0, 15, 15 +Meaghan: 0, 4, 4 +Mechelle: 0, 3, 3 +Meda: 0, 1, 1 +Mee: 0, 3, 3 +Meg: 0, 3, 3 +Megan: 0, 147, 147 +Meggan: 0, 2, 2 +Meghan: 0, 32, 32 +Meghann: 0, 2, 2 +Mei: 0, 5, 5 +Mel: 5, 0, 5 +Melaine: 0, 2, 2 +Melani: 0, 1, 1 +Melania: 0, 1, 1 +Melanie: 0, 116, 116 +Melany: 0, 2, 2 +Melba: 0, 27, 27 +Melda: 0, 1, 1 +Melia: 0, 1, 1 +Melida: 0, 1, 1 +Melina: 0, 4, 4 +Melinda: 0, 94, 94 +Melisa: 0, 13, 13 +Melissa: 0, 462, 462 +Melissia: 0, 2, 2 +Melita: 0, 1, 1 +Mellie: 0, 2, 2 +Mellisa: 0, 8, 8 +Mellissa: 0, 6, 6 +Melodee: 0, 1, 1 +Melodi: 0, 1, 1 +Melodie: 0, 6, 6 +Melody: 0, 50, 50 +Melonie: 0, 3, 3 +Melony: 0, 4, 4 +Melva: 0, 11, 11 +Melvin: 162, 0, 162 +Melvina: 0, 5, 5 +Melynda: 0, 2, 2 +Mendy: 0, 1, 1 +Mercedes: 0, 33, 33 +Mercedez: 0, 1, 1 +Mercy: 0, 3, 3 +Meredith: 0, 33, 33 +Meri: 0, 2, 2 +Merideth: 0, 1, 1 +Meridith: 0, 2, 2 +Merilyn: 0, 2, 2 +Merissa: 0, 1, 1 +Merle: 21, 0, 21 +Merlene: 0, 2, 2 +Merlin: 9, 0, 9 +Merlyn: 0, 1, 1 +Merna: 0, 2, 2 +Merri: 0, 2, 2 +Merrie: 0, 1, 1 +Merrilee: 0, 1, 1 +Merrill: 9, 0, 9 +Merry: 0, 7, 7 +Mertie: 0, 1, 1 +Mervin: 7, 0, 7 +Meryl: 0, 3, 3 +Meta: 0, 4, 4 +Mi: 0, 5, 5 +Mia: 0, 14, 14 +Mica: 0, 1, 1 +Micaela: 0, 5, 5 +Micah: 17, 0, 17 +Micha: 0, 1, 1 +Michael: 2629, 0, 2629 +Michaela: 0, 8, 8 +Michaele: 0, 1, 1 +Michal: 4, 0, 4 +Michale: 4, 0, 4 +Micheal: 123, 0, 123 +Michel: 12, 0, 12 +Michele: 0, 145, 145 +Michelina: 0, 1, 1 +Micheline: 0, 2, 2 +Michell: 0, 8, 8 +Michelle: 0, 519, 519 +Michiko: 0, 2, 2 +Mickey: 16, 0, 16 +Micki: 0, 2, 2 +Mickie: 0, 3, 3 +Miesha: 0, 1, 1 +Migdalia: 0, 7, 7 +Mignon: 0, 1, 1 +Miguel: 122, 0, 122 +Miguelina: 0, 2, 2 +Mika: 0, 2, 2 +Mikaela: 0, 1, 1 +Mike: 189, 0, 189 +Mikel: 5, 0, 5 +Miki: 0, 1, 1 +Mikki: 0, 2, 2 +Mila: 0, 2, 2 +Milagro: 0, 2, 2 +Milagros: 0, 13, 13 +Milan: 4, 0, 4 +Milda: 0, 1, 1 +Mildred: 0, 313, 313 +Miles: 17, 0, 17 +Milford: 6, 0, 6 +Milissa: 0, 3, 3 +Millard: 11, 0, 11 +Millicent: 0, 9, 9 +Millie: 0, 17, 17 +Milly: 0, 1, 1 +Milo: 5, 0, 5 +Milton: 80, 0, 80 +Mimi: 0, 5, 5 +Min: 0, 2, 2 +Mina: 0, 8, 8 +Minda: 0, 1, 1 +Mindi: 0, 3, 3 +Mindy: 0, 29, 29 +Minerva: 0, 14, 14 +Ming: 0, 1, 1 +Minh: 5, 0, 5 +Minna: 0, 2, 2 +Minnie: 0, 89, 89 +Minta: 0, 1, 1 +Miquel: 4, 0, 4 +Mira: 0, 3, 3 +Miranda: 0, 28, 28 +Mireille: 0, 2, 2 +Mirella: 0, 1, 1 +Mireya: 0, 4, 4 +Miriam: 0, 66, 66 +Mirian: 0, 4, 4 +Mirna: 0, 6, 6 +Mirta: 0, 3, 3 +Mirtha: 0, 2, 2 +Misha: 0, 1, 1 +Miss: 0, 1, 1 +Missy: 0, 6, 6 +Misti: 0, 5, 5 +Mistie: 0, 1, 1 +Misty: 0, 63, 63 +Mitch: 6, 0, 6 +Mitchel: 7, 0, 7 +Mitchell: 72, 0, 72 +Mitsue: 0, 1, 1 +Mitsuko: 0, 1, 1 +Mittie: 0, 4, 4 +Mitzi: 0, 11, 11 +Mitzie: 0, 1, 1 +Miyoko: 0, 1, 1 +Modesta: 0, 3, 3 +Modesto: 4, 0, 4 +Mohamed: 5, 0, 5 +Mohammad: 8, 0, 8 +Mohammed: 7, 0, 7 +Moira: 0, 3, 3 +Moises: 13, 0, 13 +Mollie: 0, 20, 20 +Molly: 0, 55, 55 +Mona: 0, 35, 35 +Monet: 0, 1, 1 +Monica: 0, 166, 166 +Monika: 0, 8, 8 +Monique: 0, 51, 51 +Monnie: 0, 1, 1 +Monroe: 8, 0, 8 +Monserrate: 0, 2, 2 +Monte: 15, 0, 15 +Monty: 12, 0, 12 +Moon: 0, 1, 1 +Mora: 0, 1, 1 +Morgan: 18, 0, 18 +Moriah: 0, 1, 1 +Morris: 51, 0, 51 +Morton: 7, 0, 7 +Mose: 4, 0, 4 +Moses: 20, 0, 20 +Moshe: 5, 0, 5 +Mozell: 0, 3, 3 +Mozella: 0, 2, 2 +Mozelle: 0, 5, 5 +Mui: 0, 2, 2 +Muoi: 0, 1, 1 +Muriel: 0, 38, 38 +Murray: 15, 0, 15 +My: 0, 3, 3 +Myesha: 0, 1, 1 +Myles: 7, 0, 7 +Myong: 0, 3, 3 +Myra: 0, 40, 40 +Myriam: 0, 4, 4 +Myrl: 0, 1, 1 +Myrle: 0, 2, 2 +Myrna: 0, 26, 26 +Myron: 30, 0, 30 +Myrta: 0, 1, 1 +Myrtice: 0, 3, 3 +Myrtie: 0, 2, 2 +Myrtis: 0, 5, 5 +Myrtle: 0, 78, 78 +Myung: 0, 2, 2 +Na: 0, 1, 1 +Nada: 0, 3, 3 +Nadene: 0, 1, 1 +Nadia: 0, 11, 11 +Nadine: 0, 36, 36 +Naida: 0, 1, 1 +Nakesha: 0, 1, 1 +Nakia: 0, 5, 5 +Nakisha: 0, 3, 3 +Nakita: 0, 2, 2 +Nam: 0, 1, 1 +Nan: 0, 8, 8 +Nana: 0, 2, 2 +Nancee: 0, 1, 1 +Nancey: 0, 1, 1 +Nanci: 0, 4, 4 +Nancie: 0, 2, 2 +Nancy: 0, 669, 669 +Nanette: 0, 11, 11 +Nannette: 0, 4, 4 +Nannie: 0, 13, 13 +Naoma: 0, 2, 2 +Naomi: 0, 71, 71 +Napoleon: 6, 0, 6 +Narcisa: 0, 2, 2 +Natacha: 0, 1, 1 +Natalia: 0, 10, 10 +Natalie: 0, 98, 98 +Natalya: 0, 1, 1 +Natasha: 0, 57, 57 +Natashia: 0, 1, 1 +Nathalie: 0, 5, 5 +Nathan: 185, 0, 185 +Nathanael: 4, 0, 4 +Nathanial: 4, 0, 4 +Nathaniel: 81, 0, 81 +Natisha: 0, 1, 1 +Natividad: 0, 4, 4 +Natosha: 0, 2, 2 +Neal: 37, 0, 37 +Necole: 0, 1, 1 +Ned: 12, 0, 12 +Neda: 0, 1, 1 +Nedra: 0, 5, 5 +Neely: 0, 1, 1 +Neida: 0, 1, 1 +Neil: 66, 0, 66 +Nelda: 0, 14, 14 +Nelia: 0, 2, 2 +Nelida: 0, 3, 3 +Nell: 0, 21, 21 +Nella: 0, 3, 3 +Nelle: 0, 3, 3 +Nellie: 0, 89, 89 +Nelly: 0, 8, 8 +Nelson: 61, 0, 61 +Nena: 0, 4, 4 +Nenita: 0, 1, 1 +Neoma: 0, 2, 2 +Neomi: 0, 1, 1 +Nereida: 0, 5, 5 +Nerissa: 0, 2, 2 +Nery: 0, 1, 1 +Nestor: 9, 0, 9 +Neta: 0, 2, 2 +Nettie: 0, 27, 27 +Neva: 0, 13, 13 +Nevada: 0, 1, 1 +Neville: 4, 0, 4 +Newton: 5, 0, 5 +Nga: 0, 2, 2 +Ngan: 0, 1, 1 +Ngoc: 0, 2, 2 +Nguyet: 0, 1, 1 +Nia: 0, 3, 3 +Nichelle: 0, 3, 3 +Nichol: 0, 3, 3 +Nicholas: 275, 0, 275 +Nichole: 0, 38, 38 +Nicholle: 0, 1, 1 +Nick: 43, 0, 43 +Nicki: 0, 5, 5 +Nickie: 0, 2, 2 +Nickolas: 10, 0, 10 +Nickole: 0, 2, 2 +Nicky: 4, 0, 4 +Nicol: 0, 2, 2 +Nicola: 0, 4, 4 +Nicolas: 24, 0, 24 +Nicolasa: 0, 3, 3 +Nicole: 0, 281, 281 +Nicolette: 0, 3, 3 +Nicolle: 0, 2, 2 +Nida: 0, 1, 1 +Nidia: 0, 3, 3 +Niesha: 0, 1, 1 +Nieves: 0, 2, 2 +Nigel: 6, 0, 6 +Niki: 0, 5, 5 +Nikia: 0, 2, 2 +Nikita: 0, 5, 5 +Nikki: 0, 24, 24 +Nikole: 0, 2, 2 +Nila: 0, 4, 4 +Nilda: 0, 7, 7 +Nilsa: 0, 3, 3 +Nina: 0, 72, 72 +Ninfa: 0, 3, 3 +Nisha: 0, 1, 1 +Nita: 0, 12, 12 +Noah: 22, 0, 22 +Noble: 5, 0, 5 +Nobuko: 0, 1, 1 +Noe: 11, 0, 11 +Noel: 30, 0, 30 +Noelia: 0, 3, 3 +Noella: 0, 2, 2 +Noelle: 0, 8, 8 +Noemi: 0, 12, 12 +Nohemi: 0, 1, 1 +Nola: 0, 14, 14 +Nolan: 13, 0, 13 +Noma: 0, 2, 2 +Nona: 0, 11, 11 +Nora: 0, 73, 73 +Norah: 0, 2, 2 +Norbert: 13, 0, 13 +Norberto: 6, 0, 6 +Noreen: 0, 13, 13 +Norene: 0, 3, 3 +Noriko: 0, 1, 1 +Norine: 0, 3, 3 +Norma: 0, 218, 218 +Norman: 177, 0, 177 +Normand: 6, 0, 6 +Norris: 11, 0, 11 +Nova: 0, 4, 4 +Novella: 0, 4, 4 +Nu: 0, 1, 1 +Nubia: 0, 1, 1 +Numbers: 8, 0, 8 +Nydia: 0, 4, 4 +Nyla: 0, 2, 2 +Obdulia: 0, 2, 2 +Ocie: 0, 3, 3 +Octavia: 0, 8, 8 +Octavio: 8, 0, 8 +Oda: 0, 1, 1 +Odelia: 0, 1, 1 +Odell: 10, 0, 10 +Odessa: 0, 13, 13 +Odette: 0, 3, 3 +Odilia: 0, 2, 2 +Odis: 5, 0, 5 +Ofelia: 0, 15, 15 +Ok: 0, 4, 4 +Ola: 0, 20, 20 +Olen: 5, 0, 5 +Olene: 0, 1, 1 +Oleta: 0, 5, 5 +Olevia: 0, 1, 1 +Olga: 0, 70, 70 +Olimpia: 0, 1, 1 +Olin: 6, 0, 6 +Olinda: 0, 1, 1 +Oliva: 0, 3, 3 +Olive: 0, 30, 30 +Oliver: 40, 0, 40 +Olivia: 0, 49, 49 +Ollie: 10, 0, 10 +Olympia: 0, 2, 2 +Oma: 0, 7, 7 +Omar: 36, 0, 36 +Omega: 0, 2, 2 +Omer: 5, 0, 5 +Ona: 0, 5, 5 +Oneida: 0, 2, 2 +Onie: 0, 1, 1 +Onita: 0, 1, 1 +Opal: 0, 50, 50 +Ophelia: 0, 11, 11 +Ora: 0, 27, 27 +Oralee: 0, 1, 1 +Oralia: 0, 6, 6 +Oren: 5, 0, 5 +Oretha: 0, 1, 1 +Orlando: 37, 0, 37 +Orpha: 0, 4, 4 +Orval: 5, 0, 5 +Orville: 24, 0, 24 +Oscar: 122, 0, 122 +Ossie: 0, 2, 2 +Osvaldo: 6, 0, 6 +Oswaldo: 4, 0, 4 +Otelia: 0, 1, 1 +Otha: 4, 0, 4 +Otilia: 0, 3, 3 +Otis: 41, 0, 41 +Otto: 18, 0, 18 +Ouida: 0, 3, 3 +Owen: 26, 0, 26 +Ozell: 0, 1, 1 +Ozella: 0, 2, 2 +Ozie: 0, 1, 1 +Pa: 0, 1, 1 +Pablo: 36, 0, 36 +Page: 0, 3, 3 +Paige: 0, 19, 19 +Palma: 0, 3, 3 +Palmer: 4, 0, 4 +Palmira: 0, 1, 1 +Pam: 0, 46, 46 +Pamala: 0, 8, 8 +Pamela: 0, 416, 416 +Pamelia: 0, 2, 2 +Pamella: 0, 2, 2 +Pamila: 0, 1, 1 +Pamula: 0, 1, 1 +Pandora: 0, 2, 2 +Pansy: 0, 8, 8 +Paola: 0, 3, 3 +Paris: 4, 0, 4 +Parker: 6, 0, 6 +Parthenia: 0, 1, 1 +Particia: 0, 2, 2 +Pasquale: 8, 0, 8 +Pasty: 0, 1, 1 +Pat: 22, 0, 22 +Patience: 0, 2, 2 +Patria: 0, 1, 1 +Patrica: 0, 21, 21 +Patrice: 0, 26, 26 +Patricia: 4, 0, 4 +Patrick: 389, 0, 389 +Patrina: 0, 2, 2 +Patsy: 0, 76, 76 +Patti: 0, 29, 29 +Pattie: 0, 7, 7 +Patty: 0, 43, 43 +Paul: 948, 0, 948 +Paula: 0, 217, 217 +Paulene: 0, 1, 1 +Pauletta: 0, 2, 2 +Paulette: 0, 36, 36 +Paulina: 0, 6, 6 +Pauline: 0, 165, 165 +Paulita: 0, 1, 1 +Paz: 0, 1, 1 +Pearl: 0, 94, 94 +Pearle: 0, 2, 2 +Pearlene: 0, 2, 2 +Pearlie: 0, 13, 13 +Pearline: 0, 6, 6 +Pearly: 0, 1, 1 +Pedro: 103, 0, 103 +Peg: 0, 1, 1 +Peggie: 0, 4, 4 +Peggy: 0, 208, 208 +Pei: 0, 1, 1 +Penelope: 0, 13, 13 +Penney: 0, 2, 2 +Penni: 0, 2, 2 +Pennie: 0, 4, 4 +Penny: 0, 71, 71 +Percy: 21, 0, 21 +Perla: 0, 5, 5 +Perry: 49, 0, 49 +Pete: 32, 0, 32 +Peter: 381, 0, 381 +Petra: 0, 19, 19 +Petrina: 0, 2, 2 +Petronila: 0, 1, 1 +Phebe: 0, 1, 1 +Phil: 21, 0, 21 +Philip: 197, 0, 197 +Phillip: 213, 0, 213 +Phillis: 0, 6, 6 +Philomena: 0, 5, 5 +Phoebe: 0, 9, 9 +Phung: 0, 1, 1 +Phuong: 0, 4, 4 +Phylicia: 0, 1, 1 +Phylis: 0, 4, 4 +Phyliss: 0, 4, 4 +Phyllis: 0, 219, 219 +Pia: 0, 2, 2 +Piedad: 0, 2, 2 +Pierre: 16, 0, 16 +Pilar: 0, 6, 6 +Ping: 0, 1, 1 +Pinkie: 0, 3, 3 +Piper: 0, 2, 2 +Pok: 0, 1, 1 +Polly: 0, 22, 22 +Porfirio: 5, 0, 5 +Porsche: 0, 1, 1 +Porsha: 0, 2, 2 +Porter: 4, 0, 4 +Portia: 0, 7, 7 +Precious: 0, 5, 5 +Preston: 34, 0, 34 +Pricilla: 0, 6, 6 +Prince: 5, 0, 5 +Princess: 0, 5, 5 +Priscila: 0, 1, 1 +Priscilla: 0, 71, 71 +Providencia: 0, 1, 1 +Prudence: 0, 4, 4 +Pura: 0, 1, 1 +Qiana: 0, 2, 2 +Queen: 0, 9, 9 +Queenie: 0, 2, 2 +Quentin: 15, 0, 15 +Quiana: 0, 2, 2 +Quincy: 10, 0, 10 +Quinn: 5, 0, 5 +Quintin: 5, 0, 5 +Quinton: 13, 0, 13 +Quyen: 0, 1, 1 +Rachael: 0, 38, 38 +Rachal: 0, 1, 1 +Racheal: 0, 7, 7 +Rachel: 0, 242, 242 +Rachele: 0, 2, 2 +Rachell: 0, 2, 2 +Rachelle: 0, 19, 19 +Racquel: 0, 3, 3 +Rae: 0, 14, 14 +Raeann: 0, 2, 2 +Raelene: 0, 1, 1 +Rafael: 81, 0, 81 +Rafaela: 0, 6, 6 +Raguel: 0, 1, 1 +Raina: 0, 2, 2 +Raisa: 0, 1, 1 +Raleigh: 5, 0, 5 +Ralph: 282, 0, 282 +Ramiro: 22, 0, 22 +Ramon: 90, 0, 90 +Ramona: 0, 62, 62 +Ramonita: 0, 2, 2 +Rana: 0, 2, 2 +Ranae: 0, 2, 2 +Randa: 0, 3, 3 +Randal: 20, 0, 20 +Randall: 138, 0, 138 +Randee: 0, 2, 2 +Randell: 7, 0, 7 +Randi: 0, 15, 15 +Randolph: 32, 0, 32 +Randy: 232, 0, 232 +Ranee: 0, 1, 1 +Raphael: 12, 0, 12 +Raquel: 0, 31, 31 +Rashad: 4, 0, 4 +Rasheeda: 0, 2, 2 +Rashida: 0, 3, 3 +Raul: 79, 0, 79 +Raven: 0, 5, 5 +Ray: 153, 0, 153 +Raye: 0, 2, 2 +Rayford: 4, 0, 4 +Raylene: 0, 2, 2 +Raymon: 4, 0, 4 +Raymond: 488, 0, 488 +Raymonde: 0, 1, 1 +Raymundo: 8, 0, 8 +Rayna: 0, 2, 2 +Rea: 0, 1, 1 +Reagan: 0, 1, 1 +Reanna: 0, 1, 1 +Reatha: 0, 2, 2 +Reba: 0, 24, 24 +Rebbeca: 0, 2, 2 +Rebbecca: 0, 2, 2 +Rebeca: 0, 7, 7 +Rebecca: 0, 430, 430 +Rebecka: 0, 1, 1 +Rebekah: 0, 25, 25 +Reda: 0, 2, 2 +Reed: 10, 0, 10 +Reena: 0, 1, 1 +Refugia: 0, 1, 1 +Refugio: 6, 0, 6 +Regan: 0, 3, 3 +Regena: 0, 2, 2 +Regenia: 0, 2, 2 +Reggie: 12, 0, 12 +Regina: 0, 133, 133 +Reginald: 84, 0, 84 +Regine: 0, 1, 1 +Reginia: 0, 1, 1 +Reid: 7, 0, 7 +Reiko: 0, 2, 2 +Reina: 0, 6, 6 +Reinaldo: 6, 0, 6 +Reita: 0, 1, 1 +Rema: 0, 1, 1 +Remedios: 0, 2, 2 +Remona: 0, 1, 1 +Rena: 0, 27, 27 +Renae: 0, 8, 8 +Renaldo: 4, 0, 4 +Renata: 0, 5, 5 +Renate: 0, 5, 5 +Renato: 4, 0, 4 +Renay: 0, 1, 1 +Renda: 0, 1, 1 +Rene: 48, 0, 48 +Renea: 0, 3, 3 +Renee: 0, 120, 120 +Renetta: 0, 2, 2 +Renita: 0, 6, 6 +Renna: 0, 1, 1 +Ressie: 0, 1, 1 +Reta: 0, 6, 6 +Retha: 0, 7, 7 +Retta: 0, 2, 2 +Reuben: 15, 0, 15 +Reva: 0, 10, 10 +Rex: 37, 0, 37 +Rey: 4, 0, 4 +Reyes: 5, 0, 5 +Reyna: 0, 9, 9 +Reynalda: 0, 1, 1 +Reynaldo: 18, 0, 18 +Rhea: 0, 9, 9 +Rheba: 0, 1, 1 +Rhett: 4, 0, 4 +Rhiannon: 0, 4, 4 +Rhoda: 0, 14, 14 +Rhona: 0, 1, 1 +Rhonda: 0, 162, 162 +Ria: 0, 1, 1 +Ricarda: 0, 1, 1 +Ricardo: 93, 0, 93 +Rich: 7, 0, 7 +Richard: 1703, 0, 1703 +Richelle: 0, 5, 5 +Richie: 5, 0, 5 +Rick: 91, 0, 91 +Rickey: 35, 0, 35 +Ricki: 0, 1, 1 +Rickie: 11, 0, 11 +Ricky: 141, 0, 141 +Rico: 6, 0, 6 +Rigoberto: 11, 0, 11 +Rikki: 0, 2, 2 +Riley: 10, 0, 10 +Rima: 0, 1, 1 +Rina: 0, 3, 3 +Risa: 0, 2, 2 +Rita: 0, 204, 204 +Riva: 0, 1, 1 +Rivka: 0, 1, 1 +Rob: 13, 0, 13 +Robbi: 0, 1, 1 +Robbie: 16, 0, 16 +Robbin: 0, 7, 7 +Robby: 8, 0, 8 +Robbyn: 0, 1, 1 +Robena: 0, 1, 1 +Robert: 3143, 0, 3143 +Roberta: 0, 117, 117 +Roberto: 97, 0, 97 +Robin: 32, 0, 32 +Robt: 5, 0, 5 +Robyn: 0, 39, 39 +Rocco: 11, 0, 11 +Rochel: 0, 1, 1 +Rochell: 0, 2, 2 +Rochelle: 0, 32, 32 +Rocio: 0, 8, 8 +Rocky: 16, 0, 16 +Rod: 13, 0, 13 +Roderick: 36, 0, 36 +Rodger: 17, 0, 17 +Rodney: 180, 0, 180 +Rodolfo: 30, 0, 30 +Rodrick: 6, 0, 6 +Rodrigo: 11, 0, 11 +Rogelio: 22, 0, 22 +Roger: 322, 0, 322 +Roland: 72, 0, 72 +Rolanda: 0, 3, 3 +Rolande: 0, 1, 1 +Rolando: 21, 0, 21 +Rolf: 4, 0, 4 +Rolland: 5, 0, 5 +Roma: 0, 5, 5 +Romaine: 0, 2, 2 +Roman: 20, 0, 20 +Romana: 0, 3, 3 +Romelia: 0, 2, 2 +Romeo: 10, 0, 10 +Romona: 0, 5, 5 +Ron: 72, 0, 72 +Rona: 0, 4, 4 +Ronald: 725, 0, 725 +Ronda: 0, 26, 26 +Roni: 0, 2, 2 +Ronna: 0, 3, 3 +Ronni: 0, 2, 2 +Ronnie: 113, 0, 113 +Ronny: 7, 0, 7 +Roosevelt: 28, 0, 28 +Rory: 12, 0, 12 +Rosa: 0, 194, 194 +Rosalba: 0, 5, 5 +Rosalee: 0, 7, 7 +Rosalia: 0, 8, 8 +Rosalie: 0, 39, 39 +Rosalina: 0, 5, 5 +Rosalind: 0, 18, 18 +Rosalinda: 0, 12, 12 +Rosaline: 0, 3, 3 +Rosalva: 0, 3, 3 +Rosalyn: 0, 14, 14 +Rosamaria: 0, 1, 1 +Rosamond: 0, 2, 2 +Rosana: 0, 2, 2 +Rosann: 0, 2, 2 +Rosanna: 0, 9, 9 +Rosanne: 0, 10, 10 +Rosaria: 0, 3, 3 +Rosario: 5, 0, 5 +Rosaura: 0, 3, 3 +Roscoe: 13, 0, 13 +Rose: 0, 296, 296 +Roseann: 0, 10, 10 +Roseanna: 0, 4, 4 +Roseanne: 0, 6, 6 +Roselee: 0, 1, 1 +Roselia: 0, 1, 1 +Roseline: 0, 1, 1 +Rosella: 0, 10, 10 +Roselle: 0, 1, 1 +Roselyn: 0, 5, 5 +Rosemarie: 0, 35, 35 +Rosemary: 0, 107, 107 +Rosena: 0, 1, 1 +Rosenda: 0, 1, 1 +Rosendo: 6, 0, 6 +Rosetta: 0, 22, 22 +Rosette: 0, 1, 1 +Rosia: 0, 2, 2 +Rosie: 0, 55, 55 +Rosina: 0, 4, 4 +Rosio: 0, 1, 1 +Rosita: 0, 7, 7 +Roslyn: 0, 11, 11 +Ross: 50, 0, 50 +Rossana: 0, 1, 1 +Rossie: 0, 1, 1 +Rosy: 0, 1, 1 +Rowena: 0, 9, 9 +Roxana: 0, 6, 6 +Roxane: 0, 4, 4 +Roxann: 0, 5, 5 +Roxanna: 0, 6, 6 +Roxanne: 0, 40, 40 +Roxie: 0, 11, 11 +Roxy: 0, 1, 1 +Roy: 273, 0, 273 +Royal: 6, 0, 6 +Royce: 16, 0, 16 +Rozanne: 0, 1, 1 +Rozella: 0, 2, 2 +Ruben: 82, 0, 82 +Rubi: 0, 1, 1 +Rubie: 0, 2, 2 +Rubin: 6, 0, 6 +Ruby: 0, 221, 221 +Rubye: 0, 4, 4 +Rudolf: 4, 0, 4 +Rudolph: 34, 0, 34 +Rudy: 34, 0, 34 +Rueben: 4, 0, 4 +Rufina: 0, 3, 3 +Rufus: 25, 0, 25 +Rupert: 5, 0, 5 +Russ: 7, 0, 7 +Russel: 15, 0, 15 +Russell: 224, 0, 224 +Rusty: 12, 0, 12 +Ruth: 0, 562, 562 +Rutha: 0, 1, 1 +Ruthann: 0, 4, 4 +Ruthanne: 0, 1, 1 +Ruthe: 0, 1, 1 +Ruthie: 0, 14, 14 +Ryan: 328, 0, 328 +Ryann: 0, 1, 1 +Sabina: 0, 5, 5 +Sabine: 0, 3, 3 +Sabra: 0, 3, 3 +Sabrina: 0, 57, 57 +Sacha: 0, 1, 1 +Sachiko: 0, 2, 2 +Sade: 0, 3, 3 +Sadie: 0, 39, 39 +Sadye: 0, 1, 1 +Sage: 0, 1, 1 +Sal: 5, 0, 5 +Salena: 0, 2, 2 +Salina: 0, 4, 4 +Salley: 0, 1, 1 +Sallie: 0, 24, 24 +Sally: 0, 135, 135 +Salome: 0, 2, 2 +Salvador: 49, 0, 49 +Salvatore: 29, 0, 29 +Sam: 92, 0, 92 +Samantha: 0, 124, 124 +Samara: 0, 2, 2 +Samatha: 0, 6, 6 +Samella: 0, 1, 1 +Samira: 0, 2, 2 +Sammie: 12, 0, 12 +Sammy: 25, 0, 25 +Samual: 4, 0, 4 +Samuel: 306, 0, 306 +Sana: 0, 1, 1 +Sanda: 0, 1, 1 +Sandee: 0, 1, 1 +Sandi: 0, 6, 6 +Sandie: 0, 1, 1 +Sandra: 0, 629, 629 +Sandy: 7, 0, 7 +Sanford: 9, 0, 9 +Sang: 6, 0, 6 +Sanjuana: 0, 4, 4 +Sanjuanita: 0, 4, 4 +Sanora: 0, 2, 2 +Santa: 0, 6, 6 +Santana: 0, 2, 2 +Santiago: 22, 0, 22 +Santina: 0, 2, 2 +Santo: 4, 0, 4 +Santos: 19, 0, 19 +Sara: 0, 229, 229 +Sarah: 0, 508, 508 +Sarai: 0, 1, 1 +Saran: 0, 1, 1 +Sari: 0, 2, 2 +Sarina: 0, 2, 2 +Sarita: 0, 3, 3 +Sasha: 0, 10, 10 +Saturnina: 0, 1, 1 +Sau: 0, 1, 1 +Saul: 20, 0, 20 +Saundra: 0, 13, 13 +Savanna: 0, 2, 2 +Savannah: 0, 10, 10 +Scarlet: 0, 2, 2 +Scarlett: 0, 4, 4 +Scot: 10, 0, 10 +Scott: 546, 0, 546 +Scottie: 7, 0, 7 +Scotty: 13, 0, 13 +Sean: 197, 0, 197 +Season: 0, 1, 1 +Sebastian: 10, 0, 10 +Sebrina: 0, 2, 2 +See: 0, 1, 1 +Seema: 0, 1, 1 +Selena: 0, 12, 12 +Selene: 0, 2, 2 +Selina: 0, 8, 8 +Selma: 0, 16, 16 +Sena: 0, 1, 1 +Senaida: 0, 1, 1 +September: 0, 1, 1 +Serafina: 0, 1, 1 +Serena: 0, 13, 13 +Sergio: 49, 0, 49 +Serina: 0, 2, 2 +Serita: 0, 1, 1 +Seth: 48, 0, 48 +Setsuko: 0, 1, 1 +Seymour: 7, 0, 7 +Sha: 0, 2, 2 +Shad: 4, 0, 4 +Shae: 0, 1, 1 +Shaina: 0, 4, 4 +Shakia: 0, 1, 1 +Shakira: 0, 2, 2 +Shakita: 0, 1, 1 +Shala: 0, 1, 1 +Shalanda: 0, 1, 1 +Shalon: 0, 1, 1 +Shalonda: 0, 4, 4 +Shameka: 0, 4, 4 +Shamika: 0, 4, 4 +Shan: 0, 1, 1 +Shana: 0, 18, 18 +Shanae: 0, 1, 1 +Shanda: 0, 6, 6 +Shandi: 0, 1, 1 +Shandra: 0, 3, 3 +Shane: 93, 0, 93 +Shaneka: 0, 2, 2 +Shanel: 0, 1, 1 +Shanell: 0, 3, 3 +Shanelle: 0, 2, 2 +Shani: 0, 3, 3 +Shanice: 0, 2, 2 +Shanika: 0, 3, 3 +Shaniqua: 0, 1, 1 +Shanita: 0, 3, 3 +Shanna: 0, 21, 21 +Shannan: 0, 3, 3 +Shannon: 40, 0, 40 +Shanon: 0, 5, 5 +Shanta: 0, 4, 4 +Shantae: 0, 1, 1 +Shantay: 0, 1, 1 +Shante: 0, 4, 4 +Shantel: 0, 5, 5 +Shantell: 0, 3, 3 +Shantelle: 0, 1, 1 +Shanti: 0, 1, 1 +Shaquana: 0, 1, 1 +Shaquita: 0, 1, 1 +Shara: 0, 4, 4 +Sharan: 0, 1, 1 +Sharda: 0, 1, 1 +Sharee: 0, 3, 3 +Sharell: 0, 1, 1 +Sharen: 0, 3, 3 +Shari: 0, 28, 28 +Sharice: 0, 1, 1 +Sharie: 0, 1, 1 +Sharika: 0, 1, 1 +Sharilyn: 0, 1, 1 +Sharita: 0, 2, 2 +Sharla: 0, 4, 4 +Sharleen: 0, 2, 2 +Sharlene: 0, 8, 8 +Sharmaine: 0, 1, 1 +Sharolyn: 0, 1, 1 +Sharon: 0, 522, 522 +Sharonda: 0, 5, 5 +Sharri: 0, 1, 1 +Sharron: 0, 14, 14 +Sharyl: 0, 2, 2 +Sharyn: 0, 5, 5 +Shasta: 0, 3, 3 +Shaun: 39, 0, 39 +Shauna: 0, 17, 17 +Shaunda: 0, 1, 1 +Shaunna: 0, 2, 2 +Shaunta: 0, 1, 1 +Shaunte: 0, 1, 1 +Shavon: 0, 2, 2 +Shavonda: 0, 1, 1 +Shavonne: 0, 2, 2 +Shawana: 0, 2, 2 +Shawanda: 0, 3, 3 +Shawanna: 0, 1, 1 +Shawn: 200, 0, 200 +Shawna: 0, 27, 27 +Shawnda: 0, 2, 2 +Shawnee: 0, 2, 2 +Shawnna: 0, 2, 2 +Shawnta: 0, 1, 1 +Shay: 0, 3, 3 +Shayla: 0, 6, 6 +Shayna: 0, 4, 4 +Shayne: 4, 0, 4 +Shea: 0, 2, 2 +Sheba: 0, 1, 1 +Sheena: 0, 17, 17 +Sheila: 0, 175, 175 +Sheilah: 0, 2, 2 +Shela: 0, 2, 2 +Shelba: 0, 2, 2 +Shelby: 11, 0, 11 +Sheldon: 23, 0, 23 +Shelia: 0, 43, 43 +Shella: 0, 1, 1 +Shelley: 0, 49, 49 +Shelli: 0, 5, 5 +Shellie: 0, 7, 7 +Shelly: 0, 62, 62 +Shelton: 8, 0, 8 +Shemeka: 0, 1, 1 +Shemika: 0, 2, 2 +Shena: 0, 3, 3 +Shenika: 0, 1, 1 +Shenita: 0, 2, 2 +Shenna: 0, 1, 1 +Shera: 0, 1, 1 +Sheree: 0, 10, 10 +Sherell: 0, 1, 1 +Sheri: 0, 42, 42 +Sherice: 0, 1, 1 +Sheridan: 0, 1, 1 +Sherie: 0, 4, 4 +Sherika: 0, 1, 1 +Sherill: 0, 1, 1 +Sherilyn: 0, 3, 3 +Sherise: 0, 1, 1 +Sherita: 0, 4, 4 +Sherlene: 0, 1, 1 +Sherley: 0, 2, 2 +Sherly: 0, 2, 2 +Sherlyn: 0, 2, 2 +Sherman: 28, 0, 28 +Sheron: 0, 3, 3 +Sherrell: 0, 2, 2 +Sherri: 0, 62, 62 +Sherrie: 0, 26, 26 +Sherril: 0, 1, 1 +Sherrill: 0, 4, 4 +Sherron: 0, 3, 3 +Sherry: 0, 178, 178 +Sherryl: 0, 3, 3 +Sherwood: 4, 0, 4 +Shery: 0, 2, 2 +Sheryl: 0, 59, 59 +Sheryll: 0, 1, 1 +Shiela: 0, 7, 7 +Shila: 0, 1, 1 +Shiloh: 0, 1, 1 +Shin: 0, 1, 1 +Shira: 0, 2, 2 +Shirely: 0, 1, 1 +Shirl: 0, 1, 1 +Shirlee: 0, 3, 3 +Shirleen: 0, 2, 2 +Shirlene: 0, 4, 4 +Shirley: 5, 0, 5 +Shirly: 0, 3, 3 +Shizue: 0, 1, 1 +Shizuko: 0, 1, 1 +Shon: 4, 0, 4 +Shona: 0, 2, 2 +Shonda: 0, 7, 7 +Shondra: 0, 1, 1 +Shonna: 0, 2, 2 +Shonta: 0, 1, 1 +Shoshana: 0, 2, 2 +Shu: 0, 2, 2 +Shyla: 0, 1, 1 +Sibyl: 0, 3, 3 +Sid: 4, 0, 4 +Sidney: 52, 0, 52 +Sierra: 0, 8, 8 +Signe: 0, 2, 2 +Sigrid: 0, 3, 3 +Silas: 9, 0, 9 +Silva: 0, 2, 2 +Silvana: 0, 2, 2 +Silvia: 0, 29, 29 +Sima: 0, 1, 1 +Simon: 26, 0, 26 +Simona: 0, 3, 3 +Simone: 0, 15, 15 +Simonne: 0, 1, 1 +Sina: 0, 1, 1 +Sindy: 0, 2, 2 +Siobhan: 0, 3, 3 +Sirena: 0, 1, 1 +Siu: 0, 1, 1 +Sixta: 0, 1, 1 +Skye: 0, 2, 2 +Slyvia: 0, 2, 2 +So: 0, 2, 2 +Socorro: 0, 16, 16 +Sofia: 0, 13, 13 +Soila: 0, 1, 1 +Sol: 5, 0, 5 +Solange: 0, 2, 2 +Soledad: 0, 7, 7 +Solomon: 13, 0, 13 +Somer: 0, 1, 1 +Sommer: 0, 2, 2 +Son: 12, 0, 12 +Sona: 0, 1, 1 +Sondra: 0, 18, 18 +Song: 0, 2, 2 +Sonia: 0, 68, 68 +Sonja: 0, 29, 29 +Sonny: 8, 0, 8 +Sonya: 0, 51, 51 +Soo: 0, 2, 2 +Sook: 0, 2, 2 +Soon: 0, 4, 4 +Sophia: 0, 32, 32 +Sophie: 0, 29, 29 +Soraya: 0, 2, 2 +Sparkle: 0, 1, 1 +Spencer: 30, 0, 30 +Spring: 0, 2, 2 +Stacee: 0, 1, 1 +Stacey: 11, 0, 11 +Staci: 0, 17, 17 +Stacia: 0, 5, 5 +Stacie: 0, 25, 25 +Stacy: 17, 0, 17 +Stan: 15, 0, 15 +Stanford: 5, 0, 5 +Stanley: 186, 0, 186 +Stanton: 4, 0, 4 +Star: 0, 3, 3 +Starla: 0, 4, 4 +Starr: 0, 4, 4 +Stasia: 0, 1, 1 +Stefan: 9, 0, 9 +Stefani: 0, 3, 3 +Stefania: 0, 2, 2 +Stefanie: 0, 21, 21 +Stefany: 0, 1, 1 +Steffanie: 0, 2, 2 +Stella: 0, 85, 85 +Stepanie: 0, 1, 1 +Stephaine: 0, 3, 3 +Stephan: 18, 0, 18 +Stephane: 0, 2, 2 +Stephani: 0, 3, 3 +Stephania: 0, 1, 1 +Stephanie: 0, 400, 400 +Stephany: 0, 5, 5 +Stephen: 540, 0, 540 +Stephenie: 0, 5, 5 +Stephine: 0, 3, 3 +Stephnie: 0, 1, 1 +Sterling: 16, 0, 16 +Steve: 246, 0, 246 +Steven: 780, 0, 780 +Stevie: 7, 0, 7 +Stewart: 22, 0, 22 +Stormy: 0, 3, 3 +Stuart: 44, 0, 44 +Su: 0, 3, 3 +Suanne: 0, 1, 1 +Sudie: 0, 2, 2 +Sue: 0, 111, 111 +Sueann: 0, 2, 2 +Suellen: 0, 2, 2 +Suk: 0, 2, 2 +Sulema: 0, 1, 1 +Sumiko: 0, 1, 1 +Summer: 0, 17, 17 +Sun: 0, 7, 7 +Sunday: 0, 1, 1 +Sung: 5, 0, 5 +Sunni: 0, 1, 1 +Sunny: 0, 5, 5 +Sunshine: 0, 3, 3 +Susan: 0, 794, 794 +Susana: 0, 19, 19 +Susann: 0, 3, 3 +Susanna: 0, 10, 10 +Susannah: 0, 4, 4 +Susanne: 0, 20, 20 +Susie: 0, 49, 49 +Susy: 0, 1, 1 +Suzan: 0, 6, 6 +Suzann: 0, 2, 2 +Suzanna: 0, 5, 5 +Suzanne: 0, 145, 145 +Suzette: 0, 11, 11 +Suzi: 0, 1, 1 +Suzie: 0, 3, 3 +Suzy: 0, 3, 3 +Svetlana: 0, 1, 1 +Sybil: 0, 16, 16 +Syble: 0, 3, 3 +Sydney: 7, 0, 7 +Sylvester: 28, 0, 28 +Sylvia: 0, 177, 177 +Sylvie: 0, 1, 1 +Synthia: 0, 2, 2 +Syreeta: 0, 2, 2 +Ta: 0, 1, 1 +Tabatha: 0, 13, 13 +Tabetha: 0, 2, 2 +Tabitha: 0, 27, 27 +Tad: 5, 0, 5 +Tai: 0, 1, 1 +Taina: 0, 1, 1 +Taisha: 0, 1, 1 +Tajuana: 0, 1, 1 +Takako: 0, 1, 1 +Takisha: 0, 2, 2 +Talia: 0, 3, 3 +Talisha: 0, 2, 2 +Talitha: 0, 2, 2 +Tam: 0, 2, 2 +Tama: 0, 1, 1 +Tamala: 0, 2, 2 +Tamar: 0, 3, 3 +Tamara: 0, 92, 92 +Tamatha: 0, 3, 3 +Tambra: 0, 1, 1 +Tameika: 0, 2, 2 +Tameka: 0, 13, 13 +Tamekia: 0, 2, 2 +Tamela: 0, 6, 6 +Tamera: 0, 9, 9 +Tamesha: 0, 2, 2 +Tami: 0, 27, 27 +Tamica: 0, 2, 2 +Tamie: 0, 3, 3 +Tamika: 0, 22, 22 +Tamiko: 0, 3, 3 +Tamisha: 0, 1, 1 +Tammara: 0, 2, 2 +Tammera: 0, 1, 1 +Tammi: 0, 11, 11 +Tammie: 0, 26, 26 +Tammy: 0, 259, 259 +Tamra: 0, 9, 9 +Tana: 0, 6, 6 +Tandra: 0, 1, 1 +Tandy: 0, 1, 1 +Taneka: 0, 1, 1 +Tanesha: 0, 3, 3 +Tangela: 0, 3, 3 +Tania: 0, 13, 13 +Tanika: 0, 3, 3 +Tanisha: 0, 12, 12 +Tanja: 0, 2, 2 +Tanna: 0, 2, 2 +Tanner: 6, 0, 6 +Tanya: 0, 89, 89 +Tara: 0, 107, 107 +Tarah: 0, 2, 2 +Taren: 0, 1, 1 +Tari: 0, 1, 1 +Tarra: 0, 1, 1 +Tarsha: 0, 3, 3 +Taryn: 0, 7, 7 +Tasha: 0, 30, 30 +Tashia: 0, 2, 2 +Tashina: 0, 1, 1 +Tasia: 0, 1, 1 +Tatiana: 0, 5, 5 +Tatum: 0, 1, 1 +Tatyana: 0, 1, 1 +Taunya: 0, 1, 1 +Tawana: 0, 7, 7 +Tawanda: 0, 4, 4 +Tawanna: 0, 3, 3 +Tawna: 0, 1, 1 +Tawny: 0, 2, 2 +Tawnya: 0, 4, 4 +Taylor: 24, 0, 24 +Tayna: 0, 1, 1 +Ted: 64, 0, 64 +Teddy: 18, 0, 18 +Teena: 0, 4, 4 +Tegan: 0, 1, 1 +Teisha: 0, 1, 1 +Telma: 0, 1, 1 +Temeka: 0, 2, 2 +Temika: 0, 1, 1 +Tempie: 0, 1, 1 +Temple: 0, 1, 1 +Tena: 0, 5, 5 +Tenesha: 0, 1, 1 +Tenisha: 0, 3, 3 +Tennie: 0, 2, 2 +Tennille: 0, 2, 2 +Teodora: 0, 3, 3 +Teodoro: 5, 0, 5 +Teofila: 0, 1, 1 +Tequila: 0, 2, 2 +Tera: 0, 7, 7 +Tereasa: 0, 2, 2 +Terence: 22, 0, 22 +Teresa: 0, 336, 336 +Terese: 0, 4, 4 +Teresia: 0, 1, 1 +Teresita: 0, 7, 7 +Teressa: 0, 4, 4 +Teri: 0, 33, 33 +Terica: 0, 1, 1 +Terina: 0, 1, 1 +Terisa: 0, 1, 1 +Terra: 0, 8, 8 +Terrance: 48, 0, 48 +Terrell: 20, 0, 20 +Terrence: 47, 0, 47 +Terresa: 0, 2, 2 +Terri: 0, 105, 105 +Terrie: 0, 14, 14 +Terrilyn: 0, 1, 1 +Terry: 311, 0, 311 +Tesha: 0, 2, 2 +Tess: 0, 3, 3 +Tessa: 0, 10, 10 +Tessie: 0, 7, 7 +Thad: 7, 0, 7 +Thaddeus: 12, 0, 12 +Thalia: 0, 2, 2 +Thanh: 5, 0, 5 +Thao: 0, 2, 2 +Thea: 0, 5, 5 +Theda: 0, 5, 5 +Thelma: 0, 175, 175 +Theo: 5, 0, 5 +Theodora: 0, 7, 7 +Theodore: 123, 0, 123 +Theola: 0, 2, 2 +Theresa: 0, 271, 271 +Therese: 0, 22, 22 +Theresia: 0, 2, 2 +Theressa: 0, 1, 1 +Theron: 8, 0, 8 +Thersa: 0, 3, 3 +Thi: 0, 1, 1 +Thomas: 1380, 0, 1380 +Thomasena: 0, 1, 1 +Thomasina: 0, 3, 3 +Thomasine: 0, 2, 2 +Thora: 0, 1, 1 +Thresa: 0, 2, 2 +Thu: 0, 2, 2 +Thurman: 12, 0, 12 +Thuy: 0, 4, 4 +Tia: 0, 14, 14 +Tiana: 0, 5, 5 +Tianna: 0, 2, 2 +Tiara: 0, 6, 6 +Tien: 0, 1, 1 +Tiera: 0, 1, 1 +Tierra: 0, 4, 4 +Tiesha: 0, 2, 2 +Tifany: 0, 1, 1 +Tiffaney: 0, 1, 1 +Tiffani: 0, 6, 6 +Tiffanie: 0, 4, 4 +Tiffany: 0, 195, 195 +Tiffiny: 0, 3, 3 +Tijuana: 0, 1, 1 +Tilda: 0, 1, 1 +Tillie: 0, 7, 7 +Tim: 104, 0, 104 +Timika: 0, 1, 1 +Timmy: 19, 0, 19 +Timothy: 640, 0, 640 +Tina: 0, 220, 220 +Tinisha: 0, 1, 1 +Tiny: 0, 2, 2 +Tisa: 0, 1, 1 +Tish: 0, 1, 1 +Tisha: 0, 9, 9 +Titus: 4, 0, 4 +Tobi: 0, 1, 1 +Tobias: 5, 0, 5 +Tobie: 0, 1, 1 +Toby: 19, 0, 19 +Toccara: 0, 1, 1 +Tod: 5, 0, 5 +Todd: 213, 0, 213 +Toi: 0, 1, 1 +Tom: 117, 0, 117 +Tomas: 23, 0, 23 +Tomasa: 0, 6, 6 +Tomeka: 0, 4, 4 +Tomi: 0, 1, 1 +Tomika: 0, 2, 2 +Tomiko: 0, 1, 1 +Tommie: 20, 0, 20 +Tommy: 112, 0, 112 +Tommye: 0, 1, 1 +Tomoko: 0, 2, 2 +Tona: 0, 1, 1 +Tonda: 0, 2, 2 +Tonette: 0, 1, 1 +Toney: 4, 0, 4 +Toni: 0, 64, 64 +Tonia: 0, 18, 18 +Tonie: 0, 2, 2 +Tonisha: 0, 1, 1 +Tonita: 0, 1, 1 +Tonja: 0, 5, 5 +Tony: 190, 0, 190 +Tonya: 0, 102, 102 +Tora: 0, 1, 1 +Tori: 0, 8, 8 +Torie: 0, 1, 1 +Torri: 0, 1, 1 +Torrie: 0, 1, 1 +Tory: 5, 0, 5 +Tosha: 0, 5, 5 +Toshia: 0, 1, 1 +Toshiko: 0, 2, 2 +Tova: 0, 1, 1 +Towanda: 0, 2, 2 +Toya: 0, 4, 4 +Tracee: 0, 3, 3 +Tracey: 7, 0, 7 +Traci: 0, 38, 38 +Tracie: 0, 24, 24 +Tracy: 48, 0, 48 +Tran: 0, 1, 1 +Trang: 0, 2, 2 +Travis: 166, 0, 166 +Treasa: 0, 1, 1 +Treena: 0, 1, 1 +Trena: 0, 4, 4 +Trent: 18, 0, 18 +Trenton: 9, 0, 9 +Tresa: 0, 4, 4 +Tressa: 0, 4, 4 +Tressie: 0, 3, 3 +Treva: 0, 7, 7 +Trevor: 40, 0, 40 +Trey: 6, 0, 6 +Tricia: 0, 30, 30 +Trina: 0, 24, 24 +Trinh: 0, 1, 1 +Trinidad: 5, 0, 5 +Trinity: 0, 2, 2 +Trish: 0, 3, 3 +Trisha: 0, 24, 24 +Trista: 0, 5, 5 +Tristan: 8, 0, 8 +Troy: 138, 0, 138 +Trudi: 0, 2, 2 +Trudie: 0, 2, 2 +Trudy: 0, 21, 21 +Trula: 0, 2, 2 +Truman: 9, 0, 9 +Tu: 0, 1, 1 +Tuan: 4, 0, 4 +Tula: 0, 1, 1 +Tuyet: 0, 2, 2 +Twana: 0, 2, 2 +Twanda: 0, 1, 1 +Twanna: 0, 1, 1 +Twila: 0, 9, 9 +Twyla: 0, 5, 5 +Ty: 11, 0, 11 +Tyesha: 0, 2, 2 +Tyisha: 0, 1, 1 +Tyler: 89, 0, 89 +Tynisha: 0, 1, 1 +Tyra: 0, 5, 5 +Tyree: 5, 0, 5 +Tyrell: 5, 0, 5 +Tyron: 5, 0, 5 +Tyrone: 64, 0, 64 +Tyson: 14, 0, 14 +Ula: 0, 1, 1 +Ulrike: 0, 1, 1 +Ulysses: 10, 0, 10 +Un: 0, 1, 1 +Una: 0, 6, 6 +Ursula: 0, 18, 18 +Usha: 0, 2, 2 +Ute: 0, 1, 1 +Vada: 0, 6, 6 +Val: 4, 0, 4 +Valarie: 0, 14, 14 +Valda: 0, 2, 2 +Valencia: 0, 6, 6 +Valene: 0, 1, 1 +Valentin: 7, 0, 7 +Valentina: 0, 5, 5 +Valentine: 5, 0, 5 +Valeri: 0, 1, 1 +Valeria: 0, 11, 11 +Valerie: 0, 149, 149 +Valery: 0, 2, 2 +Vallie: 0, 2, 2 +Valorie: 0, 5, 5 +Valrie: 0, 1, 1 +Van: 19, 0, 19 +Vance: 14, 0, 14 +Vanda: 0, 2, 2 +Vanesa: 0, 2, 2 +Vanessa: 0, 111, 111 +Vanetta: 0, 1, 1 +Vania: 0, 1, 1 +Vanita: 0, 2, 2 +Vanna: 0, 1, 1 +Vannesa: 0, 1, 1 +Vannessa: 0, 2, 2 +Vashti: 0, 1, 1 +Vasiliki: 0, 1, 1 +Vaughn: 11, 0, 11 +Veda: 0, 7, 7 +Velda: 0, 5, 5 +Velia: 0, 3, 3 +Vella: 0, 2, 2 +Velma: 0, 66, 66 +Velva: 0, 3, 3 +Velvet: 0, 2, 2 +Vena: 0, 2, 2 +Venessa: 0, 5, 5 +Venetta: 0, 1, 1 +Venice: 0, 2, 2 +Venita: 0, 4, 4 +Vennie: 0, 1, 1 +Venus: 0, 6, 6 +Veola: 0, 2, 2 +Vera: 0, 98, 98 +Verda: 0, 7, 7 +Verdell: 0, 2, 2 +Verdie: 0, 2, 2 +Verena: 0, 2, 2 +Vergie: 0, 4, 4 +Verla: 0, 5, 5 +Verlene: 0, 2, 2 +Verlie: 0, 2, 2 +Verline: 0, 1, 1 +Vern: 10, 0, 10 +Verna: 0, 48, 48 +Vernell: 0, 6, 6 +Vernetta: 0, 2, 2 +Vernia: 0, 1, 1 +Vernice: 0, 7, 7 +Vernie: 0, 3, 3 +Vernita: 0, 3, 3 +Vernon: 97, 0, 97 +Verona: 0, 3, 3 +Veronica: 0, 142, 142 +Veronika: 0, 1, 1 +Veronique: 0, 1, 1 +Versie: 0, 2, 2 +Vertie: 0, 1, 1 +Vesta: 0, 6, 6 +Veta: 0, 2, 2 +Vi: 0, 1, 1 +Vicenta: 0, 3, 3 +Vicente: 17, 0, 17 +Vickey: 0, 4, 4 +Vicki: 0, 109, 109 +Vickie: 0, 82, 82 +Vicky: 0, 43, 43 +Victor: 222, 0, 222 +Victoria: 0, 180, 180 +Victorina: 0, 1, 1 +Vida: 0, 7, 7 +Viki: 0, 1, 1 +Vikki: 0, 5, 5 +Vilma: 0, 10, 10 +Vina: 0, 3, 3 +Vince: 10, 0, 10 +Vincent: 168, 0, 168 +Vincenza: 0, 2, 2 +Vincenzo: 4, 0, 4 +Vinita: 0, 1, 1 +Vinnie: 0, 2, 2 +Viola: 0, 86, 86 +Violet: 0, 65, 65 +Violeta: 0, 5, 5 +Violette: 0, 2, 2 +Virgen: 0, 1, 1 +Virgie: 0, 15, 15 +Virgil: 49, 0, 49 +Virgilio: 4, 0, 4 +Virgina: 0, 7, 7 +Virginia: 0, 430, 430 +Vita: 0, 3, 3 +Vito: 9, 0, 9 +Viva: 0, 3, 3 +Vivan: 0, 2, 2 +Vivian: 0, 118, 118 +Viviana: 0, 5, 5 +Vivien: 0, 2, 2 +Vivienne: 0, 2, 2 +Von: 4, 0, 4 +Voncile: 0, 1, 1 +Vonda: 0, 8, 8 +Vonnie: 0, 3, 3 +Wade: 45, 0, 45 +Wai: 0, 2, 2 +Waldo: 5, 0, 5 +Walker: 5, 0, 5 +Wallace: 56, 0, 56 +Wally: 4, 0, 4 +Walter: 399, 0, 399 +Walton: 4, 0, 4 +Waltraud: 0, 2, 2 +Wan: 0, 1, 1 +Wanda: 0, 226, 226 +Waneta: 0, 1, 1 +Wanetta: 0, 1, 1 +Wanita: 0, 3, 3 +Ward: 10, 0, 10 +Warner: 4, 0, 4 +Warren: 110, 0, 110 +Wava: 0, 1, 1 +Waylon: 4, 0, 4 +Wayne: 249, 0, 249 +Wei: 0, 2, 2 +Weldon: 9, 0, 9 +Wen: 0, 1, 1 +Wendell: 42, 0, 42 +Wendi: 0, 10, 10 +Wendie: 0, 1, 1 +Wendolyn: 0, 1, 1 +Wendy: 0, 185, 185 +Wenona: 0, 1, 1 +Werner: 5, 0, 5 +Wes: 4, 0, 4 +Wesley: 104, 0, 104 +Weston: 6, 0, 6 +Whitley: 0, 1, 1 +Whitney: 5, 0, 5 +Wilber: 4, 0, 4 +Wilbert: 27, 0, 27 +Wilbur: 36, 0, 36 +Wilburn: 7, 0, 7 +Wilda: 0, 9, 9 +Wiley: 11, 0, 11 +Wilford: 8, 0, 8 +Wilfred: 23, 0, 23 +Wilfredo: 14, 0, 14 +Wilhelmina: 0, 7, 7 +Wilhemina: 0, 1, 1 +Will: 18, 0, 18 +Willa: 0, 16, 16 +Willard: 50, 0, 50 +Willena: 0, 1, 1 +Willene: 0, 3, 3 +Willetta: 0, 1, 1 +Willette: 0, 2, 2 +Willia: 0, 2, 2 +William: 2451, 0, 2451 +Williams: 13, 0, 13 +Willian: 4, 0, 4 +Willie: 302, 0, 302 +Williemae: 0, 1, 1 +Willis: 35, 0, 35 +Willodean: 0, 1, 1 +Willow: 0, 1, 1 +Willy: 5, 0, 5 +Wilma: 0, 99, 99 +Wilmer: 8, 0, 8 +Wilson: 28, 0, 28 +Wilton: 6, 0, 6 +Windy: 0, 4, 4 +Winford: 4, 0, 4 +Winfred: 9, 0, 9 +Winifred: 0, 27, 27 +Winnie: 0, 16, 16 +Winnifred: 0, 4, 4 +Winona: 0, 7, 7 +Winston: 19, 0, 19 +Winter: 0, 1, 1 +Wm: 33, 0, 33 +Wonda: 0, 2, 2 +Woodrow: 25, 0, 25 +Wyatt: 6, 0, 6 +Wynell: 0, 1, 1 +Wynona: 0, 3, 3 +Xavier: 13, 0, 13 +Xenia: 0, 1, 1 +Xiao: 0, 1, 1 +Xiomara: 0, 4, 4 +Xochitl: 0, 1, 1 +Xuan: 0, 1, 1 +Yadira: 0, 5, 5 +Yaeko: 0, 1, 1 +Yael: 0, 1, 1 +Yahaira: 0, 1, 1 +Yajaira: 0, 1, 1 +Yan: 0, 2, 2 +Yang: 0, 2, 2 +Yanira: 0, 2, 2 +Yasmin: 0, 4, 4 +Yasmine: 0, 1, 1 +Yasuko: 0, 1, 1 +Yee: 0, 2, 2 +Yelena: 0, 1, 1 +Yen: 0, 2, 2 +Yer: 0, 1, 1 +Yesenia: 0, 11, 11 +Yessenia: 0, 1, 1 +Yetta: 0, 3, 3 +Yevette: 0, 1, 1 +Yi: 0, 2, 2 +Ying: 0, 2, 2 +Yoko: 0, 3, 3 +Yolanda: 0, 115, 115 +Yolande: 0, 2, 2 +Yolando: 0, 2, 2 +Yolonda: 0, 4, 4 +Yon: 0, 1, 1 +Yong: 6, 0, 6 +Yoshie: 0, 1, 1 +Yoshiko: 0, 4, 4 +Youlanda: 0, 1, 1 +Young: 7, 0, 7 +Yu: 0, 3, 3 +Yuette: 0, 1, 1 +Yuk: 0, 1, 1 +Yuki: 0, 1, 1 +Yukiko: 0, 2, 2 +Yuko: 0, 2, 2 +Yulanda: 0, 1, 1 +Yun: 0, 2, 2 +Yung: 0, 1, 1 +Yuonne: 0, 1, 1 +Yuri: 0, 1, 1 +Yuriko: 0, 1, 1 +Yvette: 0, 50, 50 +Yvone: 0, 1, 1 +Yvonne: 0, 126, 126 +Zachariah: 5, 0, 5 +Zachary: 99, 0, 99 +Zachery: 8, 0, 8 +Zack: 4, 0, 4 +Zackary: 4, 0, 4 +Zada: 0, 1, 1 +Zaida: 0, 3, 3 +Zana: 0, 1, 1 +Zandra: 0, 3, 3 +Zane: 7, 0, 7 +Zelda: 0, 8, 8 +Zella: 0, 6, 6 +Zelma: 0, 13, 13 +Zena: 0, 3, 3 +Zenaida: 0, 6, 6 +Zenia: 0, 1, 1 +Zenobia: 0, 3, 3 +Zetta: 0, 1, 1 +Zina: 0, 3, 3 +Zita: 0, 2, 2 +Zoe: 0, 6, 6 +Zofia: 0, 2, 2 +Zoila: 0, 6, 6 +Zola: 0, 4, 4 +Zona: 0, 3, 3 +Zonia: 0, 1, 1 +Zora: 0, 3, 3 +Zoraida: 0, 4, 4 +Zula: 0, 3, 3 +Zulema: 0, 2, 2 +Zulma: 0, 3, 3 diff --git a/tpcdsgen/data/genders.dst b/tpcdsgen/data/genders.dst new file mode 100644 index 00000000..a1ee5fab --- /dev/null +++ b/tpcdsgen/data/genders.dst @@ -0,0 +1,7 @@ +-- gender +-- values weights +-- ====== ========== +-- 1. gender 1. uniform +------ +M: 50 +F: 50 diff --git a/tpcdsgen/data/home_class.dst b/tpcdsgen/data/home_class.dst new file mode 100644 index 00000000..c01e5d87 --- /dev/null +++ b/tpcdsgen/data/home_class.dst @@ -0,0 +1,24 @@ +------ +-- home_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +bathroom, 10: 1 +bedding, 10: 1 +kids, 10: 1 +curtains/drapes, 10: 1 +blinds/shades, 10: 1 +rugs, 10: 1 +decor, 10: 1 +lighting, 10: 1 +mattresses, 10: 1 +flatware, 10: 1 +accent, 10: 1 +paint, 10: 1 +wallpaper, 10: 1 +glassware, 10: 1 +tables, 10: 1 +furniture, 10: 1 diff --git a/tpcdsgen/data/hours.dst b/tpcdsgen/data/hours.dst new file mode 100644 index 00000000..1c90563c --- /dev/null +++ b/tpcdsgen/data/hours.dst @@ -0,0 +1,33 @@ +-- hours +-- values weights +-- ====== ========== +-- 1. hour 1. uniform +-- 2. AM/PM 2. store shopping (skewed) +-- 3. shift 3. catalog/web shopping (skewed) +-- 4. sub-shift +-- 5. meal +------ +0, AM, third, night, : 1, 0, 1 +1, AM, third, night, : 1, 0, 2 +2, AM, third, night, : 1, 0, 2 +3, AM, third, night, : 1, 0, 2 +4, AM, third, night, : 1, 0, 3 +5, AM, third, night, : 1, 0, 3 +6, AM, third, morning, breakfast: 1, 0, 3 +7, AM, first, morning, breakfast: 1, 0, 4 +8, AM, first, morning, breakfast: 1, 4, 4 +9, AM, first, morning, breakfast: 1, 8, 5 +10, AM, first, morning, : 1, 12, 5 +11, AM, first, morning, : 1, 7, 5 +12, PM, first, afternoon, lunch: 1, 8, 7 +13, PM, first, afternoon, lunch: 1, 9, 5 +14, PM, first, afternoon, lunch: 1, 12, 3 +15, PM, second, afternoon, : 1, 4, 4 +16, PM, second, afternoon, : 1, 4, 5 +17, PM, second, evening, dinner: 1, 12, 6 +18, PM, second, evening, dinner: 1, 8, 7 +19, PM, second, evening, dinner: 1, 8, 8 +20, PM, second, evening, : 1, 4, 7 +21, PM, second, evening, : 1, 0, 5 +22, PM, second, evening, : 1, 0, 3 +23, PM, third, evening, : 1, 0, 1 diff --git a/tpcdsgen/data/income_band.dst b/tpcdsgen/data/income_band.dst new file mode 100644 index 00000000..1921960d --- /dev/null +++ b/tpcdsgen/data/income_band.dst @@ -0,0 +1,29 @@ +------ +-- income_band +------ +-- values weights +-- ====== ======== +-- 1. low 1. weight +-- 2. high +------------------------ +0, 10000: 7 +10001, 20000: 7 +20001, 30000: 7 +30001, 40000: 7 +40001, 50000: 7 +50001, 60000: 7 +60001, 70000: 7 +70001, 80000: 7 +80001, 90000: 7 +90001, 100000: 7 +100001, 110000: 7 +110001, 120000: 7 +120001, 130000: 8 +130001, 140000: 8 +140001, 150000: 8 +150001, 160000: 8 +160001, 170000: 8 +170001, 180000: 8 +180001, 190000: 8 +190001, 200000: 8 + diff --git a/tpcdsgen/data/item_current_price.dst b/tpcdsgen/data/item_current_price.dst new file mode 100644 index 00000000..1f41e598 --- /dev/null +++ b/tpcdsgen/data/item_current_price.dst @@ -0,0 +1,12 @@ +------ +-- item_current_price +-- values weights +-- ----------------------- +-- 1. index 1. unified +-- 2. min 2. low +-- 3. max 3. medium +-- 4. high +------ +1, 0.09, 4.99: 60, 1, 0, 0 +2, 5.00, 9.99: 30, 0, 1, 0 +3, 10.00, 99.99:10, 0, 0, 1 diff --git a/tpcdsgen/data/item_manager_id.dst b/tpcdsgen/data/item_manager_id.dst new file mode 100644 index 00000000..37d891ed --- /dev/null +++ b/tpcdsgen/data/item_manager_id.dst @@ -0,0 +1,12 @@ +------ +-- item_manager_id +-- values weights +-- ----------------------- +-- 1. index 1. unified +-- 2. min 2. low +-- 3. max 3. medium +-- 4. high +------ +1, 1, 33: 60, 1, 0, 0 +2, 34, 66: 30, 0, 1, 0 +3, 67, 100:10, 0, 0, 1 diff --git a/tpcdsgen/data/item_manufact_id.dst b/tpcdsgen/data/item_manufact_id.dst new file mode 100644 index 00000000..348084f2 --- /dev/null +++ b/tpcdsgen/data/item_manufact_id.dst @@ -0,0 +1,12 @@ +------ +-- item_manufact_id +-- values weights +-- ----------------------- +-- 1. index 1. unified +-- 2. min 2. low +-- 3. max 3. medium +-- 4. high +------ +1, 1, 333: 60, 1, 0, 0 +2, 334, 666: 30, 0, 1, 0 +3, 667, 1000: 10, 0, 0, 1 diff --git a/tpcdsgen/data/jewelry_class.dst b/tpcdsgen/data/jewelry_class.dst new file mode 100644 index 00000000..1496d3e2 --- /dev/null +++ b/tpcdsgen/data/jewelry_class.dst @@ -0,0 +1,24 @@ +------ +-- jewelry_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +birdal, 8: 1 +diamonds, 8: 1 +gold, 8: 1 +bracelets, 8: 1 +earings, 8: 1 +rings, 8: 1 +pendants, 8: 1 +mens watch, 8: 1 +womens watch, 8: 1 +jewelry boxes, 8: 1 +semi-precious, 8: 1 +costume, 8: 1 +loose stones, 8: 1 +estate, 8: 1 +custom, 8: 1 +consignment, 8: 1 diff --git a/tpcdsgen/data/last_names.dst b/tpcdsgen/data/last_names.dst new file mode 100644 index 00000000..ab5216bc --- /dev/null +++ b/tpcdsgen/data/last_names.dst @@ -0,0 +1,5006 @@ +-- +-- Census-based last name frequency +-- values weights +-- ==== ======= +-- 1: Name 1: frequency +-- +Smith: 1006 +Johnson: 810 +Williams: 699 +Jones: 621 +Brown: 621 +Davis: 480 +Miller: 424 +Wilson: 339 +Moore: 312 +Taylor: 311 +Anderson: 311 +Thomas: 311 +Jackson: 310 +White: 279 +Harris: 275 +Martin: 273 +Thompson: 269 +Garcia: 254 +Martinez: 234 +Robinson: 233 +Clark: 231 +Rodriguez: 229 +Lewis: 226 +Lee: 220 +Walker: 219 +Hall: 200 +Allen: 199 +Young: 193 +Hernandez: 192 +King: 190 +Wright: 189 +Lopez: 187 +Hill: 187 +Scott: 185 +Green: 183 +Adams: 174 +Baker: 171 +Gonzalez: 166 +Nelson: 162 +Carter: 162 +Mitchell: 160 +Perez: 155 +Roberts: 153 +Turner: 152 +Phillips: 149 +Campbell: 149 +Parker: 146 +Evans: 141 +Edwards: 137 +Collins: 134 +Stewart: 133 +Sanchez: 130 +Morris: 125 +Rogers: 123 +Reed: 122 +Cook: 120 +Morgan: 118 +Bell: 117 +Murphy: 117 +Bailey: 115 +Rivera: 113 +Cooper: 113 +Richardson: 112 +Cox: 110 +Howard: 110 +Ward: 108 +Torres: 108 +Peterson: 107 +Gray: 106 +Ramirez: 105 +James: 105 +Watson: 103 +Brooks: 103 +Kelly: 102 +Sanders: 100 +Price: 99 +Bennett: 99 +Wood: 98 +Barnes: 97 +Ross: 96 +Henderson: 95 +Coleman: 95 +Jenkins: 95 +Perry: 94 +Powell: 93 +Long: 92 +Patterson: 92 +Hughes: 92 +Flores: 92 +Washington: 92 +Butler: 91 +Simmons: 91 +Foster: 91 +Gonzales: 87 +Bryant: 87 +Alexander: 85 +Russell: 85 +Griffin: 84 +Diaz: 84 +Hayes: 83 +Myers: 83 +Ford: 82 +Hamilton: 82 +Graham: 82 +Sullivan: 81 +Wallace: 81 +Woods: 80 +Cole: 80 +West: 80 +Jordan: 78 +Owens: 78 +Reynolds: 78 +Fisher: 77 +Ellis: 77 +Harrison: 76 +Gibson: 75 +Mcdonald: 75 +Cruz: 75 +Marshall: 75 +Ortiz: 75 +Gomez: 75 +Murray: 74 +Freeman: 74 +Wells: 73 +Webb: 72 +Simpson: 70 +Stevens: 70 +Tucker: 70 +Porter: 69 +Hunter: 69 +Hicks: 69 +Crawford: 68 +Henry: 68 +Boyd: 68 +Mason: 68 +Morales: 67 +Kennedy: 67 +Warren: 67 +Dixon: 66 +Ramos: 66 +Reyes: 66 +Burns: 65 +Gordon: 65 +Shaw: 65 +Holmes: 65 +Rice: 64 +Robertson: 64 +Hunt: 63 +Black: 63 +Daniels: 62 +Palmer: 62 +Mills: 61 +Nichols: 60 +Grant: 60 +Knight: 60 +Ferguson: 59 +Rose: 59 +Stone: 59 +Hawkins: 59 +Dunn: 58 +Perkins: 58 +Hudson: 58 +Spencer: 57 +Gardner: 57 +Stephens: 57 +Payne: 57 +Pierce: 56 +Berry: 56 +Matthews: 56 +Arnold: 56 +Wagner: 55 +Willis: 55 +Ray: 55 +Watkins: 55 +Olson: 55 +Carroll: 55 +Duncan: 55 +Snyder: 55 +Hart: 54 +Cunningham: 54 +Bradley: 54 +Lane: 54 +Andrews: 54 +Ruiz: 54 +Harper: 54 +Fox: 53 +Riley: 53 +Armstrong: 53 +Carpenter: 53 +Weaver: 53 +Greene: 53 +Lawrence: 52 +Elliott: 52 +Chavez: 52 +Sims: 52 +Austin: 52 +Peters: 52 +Kelley: 52 +Franklin: 51 +Lawson: 51 +Fields: 51 +Gutierrez: 51 +Ryan: 51 +Schmidt: 51 +Carr: 51 +Vasquez: 51 +Castillo: 51 +Wheeler: 51 +Chapman: 50 +Oliver: 50 +Montgomery: 49 +Richards: 49 +Williamson: 49 +Johnston: 49 +Banks: 48 +Meyer: 48 +Bishop: 48 +Mccoy: 48 +Howell: 48 +Alvarez: 48 +Morrison: 48 +Hansen: 47 +Fernandez: 47 +Garza: 47 +Harvey: 47 +Little: 46 +Burton: 46 +Stanley: 46 +Nguyen: 46 +George: 46 +Jacobs: 46 +Reid: 46 +Kim: 45 +Fuller: 45 +Lynch: 45 +Dean: 45 +Gilbert: 45 +Garrett: 45 +Romero: 45 +Welch: 44 +Larson: 44 +Frazier: 44 +Burke: 44 +Hanson: 43 +Day: 43 +Mendoza: 43 +Moreno: 43 +Bowman: 43 +Medina: 42 +Fowler: 42 +Brewer: 42 +Hoffman: 42 +Carlson: 42 +Silva: 42 +Pearson: 42 +Holland: 42 +Douglas: 41 +Fleming: 41 +Jensen: 41 +Vargas: 41 +Byrd: 41 +Davidson: 41 +Hopkins: 41 +May: 40 +Terry: 40 +Herrera: 40 +Wade: 40 +Soto: 40 +Walters: 40 +Curtis: 40 +Neal: 39 +Caldwell: 39 +Lowe: 39 +Jennings: 39 +Barnett: 39 +Graves: 39 +Jimenez: 39 +Horton: 39 +Shelton: 39 +Barrett: 39 +Obrien: 39 +Castro: 39 +Sutton: 38 +Gregory: 38 +Mckinney: 38 +Lucas: 38 +Miles: 38 +Craig: 38 +Rodriquez: 37 +Chambers: 37 +Holt: 37 +Lambert: 37 +Fletcher: 37 +Watts: 37 +Bates: 37 +Hale: 37 +Rhodes: 37 +Pena: 37 +Beck: 37 +Newman: 36 +Haynes: 36 +Mcdaniel: 36 +Mendez: 36 +Bush: 36 +Vaughn: 36 +Parks: 35 +Dawson: 35 +Santiago: 35 +Norris: 35 +Hardy: 35 +Love: 35 +Steele: 35 +Curry: 35 +Powers: 35 +Schultz: 35 +Barker: 35 +Guzman: 34 +Page: 34 +Munoz: 34 +Ball: 34 +Keller: 34 +Chandler: 34 +Weber: 34 +Leonard: 34 +Walsh: 33 +Lyons: 33 +Ramsey: 33 +Wolfe: 33 +Schneider: 33 +Mullins: 33 +Benson: 33 +Sharp: 33 +Bowen: 33 +Daniel: 33 +Barber: 32 +Cummings: 32 +Hines: 32 +Baldwin: 32 +Griffith: 32 +Valdez: 32 +Hubbard: 32 +Salazar: 32 +Reeves: 32 +Warner: 31 +Stevenson: 31 +Burgess: 31 +Santos: 31 +Tate: 31 +Cross: 31 +Garner: 31 +Mann: 31 +Mack: 31 +Moss: 31 +Thornton: 31 +Dennis: 31 +Mcgee: 31 +Farmer: 30 +Delgado: 30 +Aguilar: 30 +Vega: 30 +Glover: 30 +Manning: 30 +Cohen: 30 +Harmon: 30 +Rodgers: 30 +Robbins: 30 +Newton: 30 +Todd: 30 +Blair: 30 +Higgins: 30 +Ingram: 30 +Reese: 30 +Cannon: 30 +Strickland: 30 +Townsend: 30 +Potter: 30 +Goodwin: 30 +Walton: 30 +Rowe: 29 +Hampton: 29 +Ortega: 29 +Patton: 29 +Swanson: 29 +Joseph: 29 +Francis: 29 +Goodman: 29 +Maldonado: 29 +Yates: 29 +Becker: 29 +Erickson: 29 +Hodges: 29 +Rios: 29 +Conner: 29 +Adkins: 29 +Webster: 28 +Norman: 28 +Malone: 28 +Hammond: 28 +Flowers: 28 +Cobb: 28 +Moody: 28 +Quinn: 28 +Blake: 28 +Maxwell: 28 +Pope: 28 +Floyd: 27 +Osborne: 27 +Paul: 27 +Mccarthy: 27 +Guerrero: 27 +Lindsey: 27 +Estrada: 27 +Sandoval: 27 +Gibbs: 27 +Tyler: 27 +Gross: 27 +Fitzgerald: 27 +Stokes: 27 +Doyle: 27 +Sherman: 27 +Saunders: 27 +Wise: 27 +Colon: 27 +Gill: 27 +Alvarado: 27 +Greer: 26 +Padilla: 26 +Simon: 26 +Waters: 26 +Nunez: 26 +Ballard: 26 +Schwartz: 26 +Mcbride: 26 +Houston: 26 +Christensen: 26 +Klein: 26 +Pratt: 26 +Briggs: 26 +Parsons: 26 +Mclaughlin: 26 +Zimmerman: 26 +French: 26 +Buchanan: 26 +Moran: 26 +Copeland: 25 +Roy: 25 +Pittman: 25 +Brady: 25 +Mccormick: 25 +Holloway: 25 +Brock: 25 +Poole: 25 +Frank: 25 +Logan: 25 +Owen: 25 +Bass: 25 +Marsh: 25 +Drake: 25 +Wong: 25 +Jefferson: 25 +Park: 25 +Morton: 25 +Abbott: 25 +Sparks: 25 +Patrick: 24 +Norton: 24 +Huff: 24 +Clayton: 24 +Massey: 24 +Lloyd: 24 +Figueroa: 24 +Carson: 24 +Bowers: 24 +Roberson: 24 +Barton: 24 +Tran: 24 +Lamb: 24 +Harrington: 24 +Casey: 24 +Boone: 24 +Cortez: 24 +Clarke: 24 +Mathis: 24 +Singleton: 24 +Wilkins: 24 +Cain: 24 +Bryan: 24 +Underwood: 24 +Hogan: 24 +Mckenzie: 23 +Collier: 23 +Luna: 23 +Phelps: 23 +Mcguire: 23 +Allison: 23 +Bridges: 23 +Wilkerson: 23 +Nash: 23 +Summers: 23 +Atkins: 23 +Wilcox: 23 +Pitts: 23 +Conley: 23 +Marquez: 23 +Burnett: 23 +Richard: 23 +Cochran: 23 +Chase: 23 +Davenport: 23 +Hood: 23 +Gates: 23 +Clay: 23 +Ayala: 23 +Sawyer: 23 +Roman: 23 +Vazquez: 23 +Dickerson: 23 +Hodge: 22 +Acosta: 22 +Flynn: 22 +Espinoza: 22 +Nicholson: 22 +Monroe: 22 +Wolf: 22 +Morrow: 22 +Kirk: 22 +Randall: 22 +Anthony: 22 +Whitaker: 22 +Oconnor: 22 +Skinner: 22 +Ware: 22 +Molina: 22 +Kirby: 22 +Huffman: 22 +Bradford: 22 +Charles: 22 +Gilmore: 22 +Dominguez: 22 +Oneal: 22 +Bruce: 22 +Lang: 21 +Combs: 21 +Kramer: 21 +Heath: 21 +Hancock: 21 +Gallagher: 21 +Gaines: 21 +Shaffer: 21 +Short: 21 +Wiggins: 21 +Mathews: 21 +Mcclain: 21 +Fischer: 21 +Wall: 21 +Small: 21 +Melton: 21 +Hensley: 21 +Bond: 21 +Dyer: 21 +Cameron: 21 +Grimes: 21 +Contreras: 21 +Christian: 21 +Wyatt: 21 +Baxter: 21 +Snow: 21 +Mosley: 21 +Shepherd: 21 +Larsen: 21 +Hoover: 21 +Beasley: 20 +Glenn: 20 +Petersen: 20 +Whitehead: 20 +Meyers: 20 +Keith: 20 +Garrison: 20 +Vincent: 20 +Shields: 20 +Horn: 20 +Savage: 20 +Olsen: 20 +Schroeder: 20 +Hartman: 20 +Woodard: 20 +Mueller: 20 +Kemp: 20 +Deleon: 20 +Booth: 20 +Patel: 20 +Calhoun: 20 +Wiley: 20 +Eaton: 20 +Cline: 20 +Navarro: 20 +Harrell: 20 +Lester: 20 +Humphrey: 20 +Parrish: 20 +Duran: 20 +Hutchinson: 20 +Hess: 20 +Dorsey: 20 +Bullock: 20 +Robles: 20 +Beard: 19 +Dalton: 19 +Avila: 19 +Vance: 19 +Rich: 19 +Blackwell: 19 +York: 19 +Johns: 19 +Blankenship: 19 +Trevino: 19 +Salinas: 19 +Campos: 19 +Pruitt: 19 +Moses: 19 +Callahan: 19 +Golden: 19 +Montoya: 19 +Hardin: 19 +Guerra: 19 +Mcdowell: 19 +Carey: 19 +Stafford: 19 +Gallegos: 19 +Henson: 19 +Wilkinson: 19 +Booker: 19 +Merritt: 19 +Miranda: 19 +Atkinson: 19 +Orr: 19 +Decker: 19 +Hobbs: 19 +Preston: 19 +Tanner: 19 +Knox: 19 +Pacheco: 19 +Stephenson: 18 +Glass: 18 +Rojas: 18 +Serrano: 18 +Marks: 18 +Hickman: 18 +English: 18 +Sweeney: 18 +Strong: 18 +Prince: 18 +Mcclure: 18 +Conway: 18 +Walter: 18 +Roth: 18 +Maynard: 18 +Farrell: 18 +Lowery: 18 +Hurst: 18 +Nixon: 18 +Weiss: 18 +Trujillo: 18 +Ellison: 18 +Sloan: 18 +Juarez: 18 +Winters: 18 +Mclean: 18 +Randolph: 18 +Leon: 18 +Boyer: 18 +Villarreal: 18 +Mccall: 18 +Gentry: 18 +Carrillo: 17 +Kent: 17 +Ayers: 17 +Lara: 17 +Shannon: 17 +Sexton: 17 +Pace: 17 +Hull: 17 +Leblanc: 17 +Browning: 17 +Velasquez: 17 +Leach: 17 +Chang: 17 +House: 17 +Sellers: 17 +Herring: 17 +Noble: 17 +Foley: 17 +Bartlett: 17 +Mercado: 17 +Landry: 17 +Durham: 17 +Walls: 17 +Barr: 17 +Mckee: 17 +Bauer: 17 +Rivers: 17 +Everett: 17 +Bradshaw: 17 +Pugh: 17 +Velez: 17 +Rush: 17 +Estes: 17 +Dodson: 17 +Morse: 17 +Sheppard: 17 +Weeks: 17 +Camacho: 17 +Bean: 17 +Barron: 17 +Livingston: 17 +Middleton: 16 +Spears: 16 +Branch: 16 +Blevins: 16 +Chen: 16 +Kerr: 16 +Mcconnell: 16 +Hatfield: 16 +Harding: 16 +Ashley: 16 +Solis: 16 +Herman: 16 +Frost: 16 +Giles: 16 +Blackburn: 16 +William: 16 +Pennington: 16 +Woodward: 16 +Finley: 16 +Mcintosh: 16 +Koch: 16 +Best: 16 +Solomon: 16 +Mccullough: 16 +Dudley: 16 +Nolan: 16 +Blanchard: 16 +Rivas: 16 +Brennan: 16 +Mejia: 16 +Kane: 16 +Benton: 16 +Joyce: 16 +Buckley: 16 +Haley: 16 +Valentine: 16 +Maddox: 16 +Russo: 16 +Mcknight: 16 +Buck: 16 +Moon: 16 +Mcmillan: 16 +Crosby: 16 +Berg: 16 +Dotson: 16 +Mays: 16 +Roach: 16 +Church: 16 +Chan: 16 +Richmond: 16 +Meadows: 16 +Faulkner: 16 +Oneill: 16 +Knapp: 16 +Kline: 15 +Barry: 15 +Ochoa: 15 +Jacobson: 15 +Gay: 15 +Avery: 15 +Hendricks: 15 +Horne: 15 +Shepard: 15 +Hebert: 15 +Cherry: 15 +Cardenas: 15 +Mcintyre: 15 +Whitney: 15 +Waller: 15 +Holman: 15 +Donaldson: 15 +Cantu: 15 +Terrell: 15 +Morin: 15 +Gillespie: 15 +Fuentes: 15 +Tillman: 15 +Sanford: 15 +Bentley: 15 +Peck: 15 +Key: 15 +Salas: 15 +Rollins: 15 +Gamble: 15 +Dickson: 15 +Battle: 15 +Santana: 15 +Cabrera: 15 +Cervantes: 15 +Howe: 15 +Hinton: 15 +Hurley: 15 +Spence: 15 +Zamora: 15 +Yang: 15 +Mcneil: 15 +Suarez: 15 +Case: 15 +Petty: 15 +Gould: 15 +Mcfarland: 15 +Sampson: 15 +Carver: 15 +Bray: 15 +Rosario: 15 +Macdonald: 15 +Stout: 15 +Hester: 15 +Melendez: 15 +Dillon: 15 +Farley: 15 +Hopper: 15 +Galloway: 15 +Potts: 15 +Bernard: 15 +Joyner: 14 +Stein: 14 +Aguirre: 14 +Osborn: 14 +Mercer: 14 +Bender: 14 +Franco: 14 +Rowland: 14 +Sykes: 14 +Benjamin: 14 +Travis: 14 +Pickett: 14 +Crane: 14 +Sears: 14 +Mayo: 14 +Dunlap: 14 +Hayden: 14 +Wilder: 14 +Mckay: 14 +Coffey: 14 +Mccarty: 14 +Ewing: 14 +Cooley: 14 +Vaughan: 14 +Bonner: 14 +Cotton: 14 +Holder: 14 +Stark: 14 +Ferrell: 14 +Cantrell: 14 +Fulton: 14 +Lynn: 14 +Lott: 14 +Calderon: 14 +Rosa: 14 +Pollard: 14 +Hooper: 14 +Burch: 14 +Mullen: 14 +Fry: 14 +Riddle: 14 +Levy: 14 +David: 14 +Duke: 14 +Odonnell: 14 +Guy: 14 +Michael: 14 +Britt: 14 +Frederick: 14 +Daugherty: 14 +Berger: 14 +Dillard: 14 +Alston: 14 +Jarvis: 14 +Frye: 14 +Riggs: 14 +Chaney: 14 +Odom: 13 +Duffy: 13 +Fitzpatrick: 13 +Valenzuela: 13 +Merrill: 13 +Mayer: 13 +Alford: 13 +Mcpherson: 13 +Acevedo: 13 +Donovan: 13 +Barrera: 13 +Albert: 13 +Cote: 13 +Reilly: 13 +Compton: 13 +Raymond: 13 +Mooney: 13 +Mcgowan: 13 +Craft: 13 +Cleveland: 13 +Clemons: 13 +Wynn: 13 +Nielsen: 13 +Baird: 13 +Stanton: 13 +Snider: 13 +Rosales: 13 +Bright: 13 +Witt: 13 +Stuart: 13 +Hays: 13 +Holden: 13 +Rutledge: 13 +Kinney: 13 +Clements: 13 +Castaneda: 13 +Slater: 13 +Hahn: 13 +Emerson: 13 +Conrad: 13 +Burks: 13 +Delaney: 13 +Pate: 13 +Lancaster: 13 +Sweet: 13 +Justice: 13 +Tyson: 13 +Sharpe: 13 +Whitfield: 13 +Talley: 13 +Macias: 13 +Irwin: 13 +Burris: 13 +Ratliff: 13 +Mccray: 13 +Madden: 13 +Kaufman: 13 +Beach: 13 +Goff: 13 +Cash: 13 +Bolton: 13 +Mcfadden: 13 +Levine: 13 +Good: 13 +Byers: 13 +Kirkland: 13 +Kidd: 13 +Workman: 13 +Carney: 13 +Dale: 13 +Mcleod: 13 +Holcomb: 13 +England: 13 +Finch: 13 +Head: 12 +Burt: 12 +Hendrix: 12 +Sosa: 12 +Haney: 12 +Franks: 12 +Sargent: 12 +Nieves: 12 +Downs: 12 +Rasmussen: 12 +Bird: 12 +Hewitt: 12 +Lindsay: 12 +Le: 12 +Foreman: 12 +Valencia: 12 +Oneil: 12 +Delacruz: 12 +Vinson: 12 +Dejesus: 12 +Hyde: 12 +Forbes: 12 +Gilliam: 12 +Guthrie: 12 +Wooten: 12 +Huber: 12 +Barlow: 12 +Boyle: 12 +Mcmahon: 12 +Buckner: 12 +Rocha: 12 +Puckett: 12 +Langley: 12 +Knowles: 12 +Cooke: 12 +Velazquez: 12 +Whitley: 12 +Noel: 12 +Vang: 12 +Shea: 12 +Rouse: 12 +Hartley: 12 +Mayfield: 12 +Elder: 12 +Rankin: 12 +Hanna: 12 +Cowan: 12 +Lucero: 12 +Arroyo: 12 +Slaughter: 12 +Haas: 12 +Oconnell: 12 +Minor: 12 +Kendrick: 12 +Shirley: 12 +Kendall: 12 +Boucher: 12 +Archer: 12 +Boggs: 12 +Odell: 12 +Dougherty: 12 +Andersen: 12 +Newell: 12 +Crowe: 12 +Wang: 12 +Friedman: 12 +Bland: 12 +Swain: 12 +Holley: 12 +Felix: 12 +Pearce: 12 +Childs: 12 +Yarbrough: 12 +Galvan: 12 +Proctor: 12 +Meeks: 12 +Lozano: 12 +Mora: 12 +Rangel: 12 +Bacon: 12 +Villanueva: 12 +Schaefer: 12 +Rosado: 12 +Helms: 12 +Boyce: 12 +Goss: 12 +Stinson: 11 +Smart: 11 +Lake: 11 +Ibarra: 11 +Hutchins: 11 +Covington: 11 +Reyna: 11 +Gregg: 11 +Werner: 11 +Crowley: 11 +Hatcher: 11 +Mackey: 11 +Bunch: 11 +Womack: 11 +Polk: 11 +Jamison: 11 +Dodd: 11 +Childress: 11 +Childers: 11 +Camp: 11 +Villa: 11 +Dye: 11 +Springer: 11 +Mahoney: 11 +Dailey: 11 +Belcher: 11 +Lockhart: 11 +Griggs: 11 +Costa: 11 +Connor: 11 +Brandt: 11 +Winter: 11 +Walden: 11 +Moser: 11 +Tracy: 11 +Tatum: 11 +Mccann: 11 +Akers: 11 +Lutz: 11 +Pryor: 11 +Law: 11 +Orozco: 11 +Mcallister: 11 +Lugo: 11 +Davies: 11 +Shoemaker: 11 +Madison: 11 +Rutherford: 11 +Newsome: 11 +Magee: 11 +Chamberlain: 11 +Blanton: 11 +Simms: 11 +Godfrey: 11 +Flanagan: 11 +Crum: 11 +Cordova: 11 +Escobar: 11 +Downing: 11 +Sinclair: 11 +Donahue: 11 +Krueger: 11 +Mcginnis: 11 +Gore: 11 +Farris: 11 +Webber: 11 +Corbett: 11 +Andrade: 11 +Starr: 11 +Lyon: 11 +Yoder: 11 +Hastings: 11 +Mcgrath: 11 +Spivey: 11 +Krause: 11 +Harden: 11 +Crabtree: 11 +Kirkpatrick: 11 +Hollis: 11 +Brandon: 11 +Arrington: 11 +Ervin: 11 +Clifton: 11 +Ritter: 11 +Mcghee: 11 +Bolden: 11 +Maloney: 11 +Gagnon: 11 +Dunbar: 11 +Ponce: 11 +Pike: 11 +Mayes: 11 +Heard: 11 +Beatty: 11 +Mobley: 11 +Kimball: 11 +Butts: 11 +Montes: 11 +Herbert: 11 +Grady: 11 +Eldridge: 11 +Braun: 11 +Hamm: 11 +Gibbons: 11 +Seymour: 11 +Moyer: 11 +Manley: 11 +Herron: 11 +Plummer: 11 +Elmore: 11 +Cramer: 11 +Gary: 11 +Rucker: 11 +Hilton: 11 +Blue: 11 +Pierson: 11 +Fontenot: 11 +Field: 11 +Rubio: 11 +Grace: 11 +Goldstein: 11 +Elkins: 11 +Wills: 10 +Novak: 10 +John: 10 +Hickey: 10 +Worley: 10 +Gorman: 10 +Katz: 10 +Dickinson: 10 +Broussard: 10 +Fritz: 10 +Woodruff: 10 +Crow: 10 +Christopher: 10 +Britton: 10 +Forrest: 10 +Nance: 10 +Lehman: 10 +Bingham: 10 +Zuniga: 10 +Whaley: 10 +Shafer: 10 +Coffman: 10 +Steward: 10 +Delarosa: 10 +Nix: 10 +Neely: 10 +Numbers: 10 +Mata: 10 +Manuel: 10 +Davila: 10 +Mccabe: 10 +Kessler: 10 +Emery: 10 +Bowling: 10 +Hinkle: 10 +Welsh: 10 +Pagan: 10 +Goldberg: 10 +Goins: 10 +Crouch: 10 +Cuevas: 10 +Quinones: 10 +Mcdermott: 10 +Hendrickson: 10 +Samuels: 10 +Denton: 10 +Bergeron: 10 +Lam: 10 +Ivey: 10 +Locke: 10 +Haines: 10 +Thurman: 10 +Snell: 10 +Hoskins: 10 +Byrne: 10 +Milton: 10 +Winston: 10 +Arthur: 10 +Arias: 10 +Stanford: 10 +Roe: 10 +Corbin: 10 +Beltran: 10 +Chappell: 10 +Hurt: 10 +Downey: 10 +Dooley: 10 +Tuttle: 10 +Couch: 10 +Payton: 10 +Mcelroy: 10 +Crockett: 10 +Groves: 10 +Clement: 10 +Leslie: 10 +Cartwright: 10 +Dickey: 10 +Mcgill: 10 +Dubois: 10 +Muniz: 10 +Erwin: 10 +Self: 10 +Tolbert: 10 +Dempsey: 10 +Cisneros: 10 +Sewell: 10 +Latham: 10 +Garland: 10 +Vigil: 10 +Tapia: 10 +Sterling: 10 +Rainey: 10 +Norwood: 10 +Lacy: 10 +Stroud: 10 +Meade: 10 +Amos: 10 +Tipton: 10 +Lord: 10 +Kuhn: 10 +Hilliard: 10 +Bonilla: 10 +Teague: 10 +Courtney: 10 +Gunn: 10 +Ho: 10 +Greenwood: 10 +Correa: 10 +Reece: 10 +Weston: 10 +Poe: 10 +Trent: 10 +Pineda: 10 +Phipps: 10 +Frey: 10 +Kaiser: 10 +Ames: 10 +Paige: 10 +Gunter: 10 +Schmitt: 10 +Milligan: 10 +Espinosa: 10 +Carlton: 10 +Bowden: 10 +Vickers: 10 +Lowry: 10 +Pritchard: 10 +Costello: 10 +Piper: 9 +Mcclellan: 9 +Lovell: 9 +Drew: 9 +Sheehan: 9 +Quick: 9 +Hatch: 9 +Dobson: 9 +Singh: 9 +Jeffries: 9 +Hollingsworth: 9 +Sorensen: 9 +Meza: 9 +Fink: 9 +Donnelly: 9 +Burrell: 9 +Bruno: 9 +Tomlinson: 9 +Colbert: 9 +Billings: 9 +Ritchie: 9 +Helton: 9 +Sutherland: 9 +Peoples: 9 +Mcqueen: 9 +Gaston: 9 +Thomason: 9 +Mckinley: 9 +Givens: 9 +Crocker: 9 +Vogel: 9 +Robison: 9 +Dunham: 9 +Coker: 9 +Swartz: 9 +Keys: 9 +Lilly: 9 +Ladner: 9 +Hannah: 9 +Willard: 9 +Richter: 9 +Hargrove: 9 +Edmonds: 9 +Brantley: 9 +Albright: 9 +Murdock: 9 +Boswell: 9 +Muller: 9 +Quintero: 9 +Padgett: 9 +Kenney: 9 +Daly: 9 +Connolly: 9 +Pierre: 9 +Inman: 9 +Quintana: 9 +Lund: 9 +Barnard: 9 +Villegas: 9 +Simons: 9 +Land: 9 +Huggins: 9 +Tidwell: 9 +Sanderson: 9 +Bullard: 9 +Mcclendon: 9 +Duarte: 9 +Draper: 9 +Meredith: 9 +Marrero: 9 +Dwyer: 9 +Abrams: 9 +Stover: 9 +Goode: 9 +Fraser: 9 +Crews: 9 +Bernal: 9 +Smiley: 9 +Godwin: 9 +Fish: 9 +Conklin: 9 +Mcneal: 9 +Baca: 9 +Esparza: 9 +Crowder: 9 +Bower: 9 +Nicholas: 9 +Chung: 9 +Brewster: 9 +Mcneill: 9 +Dick: 9 +Rodrigues: 9 +Leal: 9 +Coates: 9 +Raines: 9 +Mccain: 9 +Mccord: 9 +Miner: 9 +Holbrook: 9 +Swift: 9 +Dukes: 9 +Carlisle: 9 +Aldridge: 9 +Ackerman: 9 +Starks: 9 +Ricks: 9 +Holliday: 9 +Ferris: 9 +Hairston: 9 +Sheffield: 9 +Lange: 9 +Fountain: 9 +Marino: 9 +Doss: 9 +Betts: 9 +Kaplan: 9 +Carmichael: 9 +Bloom: 9 +Ruffin: 9 +Penn: 9 +Kern: 9 +Bowles: 9 +Sizemore: 9 +Larkin: 9 +Dupree: 9 +Jewell: 9 +Silver: 9 +Seals: 9 +Metcalf: 9 +Hutchison: 9 +Henley: 9 +Farr: 9 +Castle: 9 +Mccauley: 9 +Hankins: 9 +Gustafson: 9 +Deal: 9 +Curran: 9 +Ash: 9 +Waddell: 9 +Ramey: 9 +Cates: 9 +Pollock: 9 +Major: 9 +Irvin: 9 +Cummins: 9 +Messer: 9 +Heller: 9 +Dewitt: 9 +Lin: 9 +Funk: 9 +Cornett: 9 +Palacios: 9 +Galindo: 9 +Cano: 9 +Hathaway: 9 +Singer: 8 +Pham: 8 +Enriquez: 8 +Aaron: 8 +Salgado: 8 +Pelletier: 8 +Painter: 8 +Wiseman: 8 +Blount: 8 +Hand: 8 +Feliciano: 8 +Temple: 8 +Houser: 8 +Doherty: 8 +Mead: 8 +Mcgraw: 8 +Toney: 8 +Swan: 8 +Melvin: 8 +Capps: 8 +Blanco: 8 +Blackmon: 8 +Wesley: 8 +Thomson: 8 +Mcmanus: 8 +Fair: 8 +Burkett: 8 +Post: 8 +Gleason: 8 +Rudolph: 8 +Ott: 8 +Dickens: 8 +Cormier: 8 +Voss: 8 +Rushing: 8 +Rosenberg: 8 +Hurd: 8 +Dumas: 8 +Benitez: 8 +Arellano: 8 +Story: 8 +Marin: 8 +Caudill: 8 +Bragg: 8 +Jaramillo: 8 +Huerta: 8 +Gipson: 8 +Colvin: 8 +Biggs: 8 +Vela: 8 +Platt: 8 +Cassidy: 8 +Tompkins: 8 +Mccollum: 8 +Kay: 8 +Gabriel: 8 +Dolan: 8 +Daley: 8 +Crump: 8 +Street: 8 +Sneed: 8 +Kilgore: 8 +Grove: 8 +Grimm: 8 +Davison: 8 +Brunson: 8 +Prater: 8 +Marcum: 8 +Devine: 8 +Kyle: 8 +Dodge: 8 +Stratton: 8 +Rosas: 8 +Choi: 8 +Tripp: 8 +Ledbetter: 8 +Lay: 8 +Hightower: 8 +Haywood: 8 +Feldman: 8 +Epps: 8 +Yeager: 8 +Posey: 8 +Sylvester: 8 +Scruggs: 8 +Cope: 8 +Stubbs: 8 +Richey: 8 +Overton: 8 +Trotter: 8 +Sprague: 8 +Cordero: 8 +Butcher: 8 +Burger: 8 +Stiles: 8 +Burgos: 8 +Woodson: 8 +Horner: 8 +Bassett: 8 +Purcell: 8 +Haskins: 8 +Gee: 8 +Akins: 8 +Abraham: 8 +Hoyt: 8 +Ziegler: 8 +Spaulding: 8 +Hadley: 8 +Grubbs: 8 +Sumner: 8 +Murillo: 8 +Zavala: 8 +Shook: 8 +Lockwood: 8 +Jarrett: 8 +Driscoll: 8 +Dahl: 8 +Thorpe: 8 +Sheridan: 8 +Redmond: 8 +Putnam: 8 +Mcwilliams: 8 +Mcrae: 8 +Cornell: 8 +Felton: 8 +Romano: 8 +Joiner: 8 +Sadler: 8 +Hedrick: 8 +Hager: 8 +Hagen: 8 +Fitch: 8 +Coulter: 8 +Thacker: 8 +Mansfield: 8 +Langston: 8 +Guidry: 8 +Ferreira: 8 +Corley: 8 +Conn: 8 +Rossi: 8 +Lackey: 8 +Cody: 8 +Baez: 8 +Saenz: 8 +Mcnamara: 8 +Darnell: 8 +Michel: 8 +Mcmullen: 8 +Mckenna: 8 +Mcdonough: 8 +Link: 8 +Engel: 8 +Browne: 8 +Roper: 8 +Peacock: 8 +Eubanks: 8 +Drummond: 8 +Stringer: 8 +Pritchett: 8 +Parham: 8 +Mims: 8 +Landers: 8 +Ham: 8 +Grayson: 8 +Stacy: 8 +Schafer: 8 +Egan: 8 +Timmons: 8 +Ohara: 8 +Keen: 8 +Hamlin: 8 +Finn: 8 +Cortes: 8 +Mcnair: 8 +Louis: 8 +Clifford: 8 +Nadeau: 8 +Moseley: 8 +Michaud: 8 +Rosen: 8 +Oakes: 8 +Kurtz: 8 +Jeffers: 8 +Calloway: 8 +Beal: 8 +Bautista: 8 +Winn: 8 +Suggs: 8 +Stern: 8 +Stapleton: 8 +Lyles: 8 +Laird: 8 +Montano: 8 +Diamond: 8 +Dawkins: 8 +Roland: 8 +Hagan: 8 +Goldman: 8 +Bryson: 8 +Barajas: 8 +Lovett: 8 +Segura: 8 +Metz: 8 +Lockett: 8 +Langford: 8 +Hinson: 8 +Eastman: 8 +Rock: 8 +Hooks: 8 +Woody: 7 +Smallwood: 7 +Shapiro: 7 +Crowell: 7 +Whalen: 7 +Triplett: 7 +Hooker: 7 +Chatman: 7 +Aldrich: 7 +Cahill: 7 +Youngblood: 7 +Ybarra: 7 +Stallings: 7 +Sheets: 7 +Samuel: 7 +Reeder: 7 +Person: 7 +Pack: 7 +Lacey: 7 +Connelly: 7 +Bateman: 7 +Abernathy: 7 +Winkler: 7 +Wilkes: 7 +Masters: 7 +Hackett: 7 +Granger: 7 +Gillis: 7 +Schmitz: 7 +Sapp: 7 +Napier: 7 +Souza: 7 +Lanier: 7 +Gomes: 7 +Weir: 7 +Otero: 7 +Ledford: 7 +Burroughs: 7 +Babcock: 7 +Ventura: 7 +Siegel: 7 +Dugan: 7 +Clinton: 7 +Christie: 7 +Bledsoe: 7 +Atwood: 7 +Wray: 7 +Varner: 7 +Spangler: 7 +Otto: 7 +Anaya: 7 +Staley: 7 +Kraft: 7 +Fournier: 7 +Eddy: 7 +Belanger: 7 +Wolff: 7 +Thorne: 7 +Bynum: 7 +Burnette: 7 +Boykin: 7 +Swenson: 7 +Purvis: 7 +Pina: 7 +Khan: 7 +Duvall: 7 +Darby: 7 +Xiong: 7 +Kauffman: 7 +Ali: 7 +Yu: 7 +Healy: 7 +Engle: 7 +Corona: 7 +Benoit: 7 +Valle: 7 +Steiner: 7 +Spicer: 7 +Shaver: 7 +Randle: 7 +Lundy: 7 +Dow: 7 +Chin: 7 +Calvert: 7 +Staton: 7 +Neff: 7 +Kearney: 7 +Darden: 7 +Oakley: 7 +Medeiros: 7 +Mccracken: 7 +Crenshaw: 7 +Block: 7 +Beaver: 7 +Perdue: 7 +Dill: 7 +Whittaker: 7 +Tobin: 7 +Cornelius: 7 +Washburn: 7 +Hogue: 7 +Goodrich: 7 +Easley: 7 +Bravo: 7 +Dennison: 7 +Vera: 7 +Shipley: 7 +Kerns: 7 +Jorgensen: 7 +Crain: 7 +Abel: 7 +Villalobos: 7 +Maurer: 7 +Longoria: 7 +Keene: 7 +Coon: 7 +Sierra: 7 +Witherspoon: 7 +Staples: 7 +Pettit: 7 +Kincaid: 7 +Eason: 7 +Madrid: 7 +Echols: 7 +Lusk: 7 +Wu: 7 +Stahl: 7 +Currie: 7 +Thayer: 7 +Shultz: 7 +Sherwood: 7 +Mcnally: 7 +Seay: 7 +North: 7 +Maher: 7 +Kenny: 7 +Hope: 7 +Gagne: 7 +Barrow: 7 +Nava: 7 +Myles: 7 +Moreland: 7 +Honeycutt: 7 +Hearn: 7 +Diggs: 7 +Caron: 7 +Whitten: 7 +Westbrook: 7 +Stovall: 7 +Ragland: 7 +Queen: 7 +Munson: 7 +Meier: 7 +Looney: 7 +Kimble: 7 +Jolly: 7 +Hobson: 7 +London: 7 +Goddard: 7 +Culver: 7 +Burr: 7 +Presley: 7 +Negron: 7 +Connell: 7 +Tovar: 7 +Marcus: 7 +Huddleston: 7 +Hammer: 7 +Ashby: 7 +Salter: 7 +Root: 7 +Pendleton: 7 +Oleary: 7 +Nickerson: 7 +Myrick: 7 +Judd: 7 +Jacobsen: 7 +Elliot: 7 +Bain: 7 +Adair: 7 +Starnes: 7 +Sheldon: 7 +Matos: 7 +Light: 7 +Busby: 7 +Herndon: 7 +Hanley: 7 +Bellamy: 7 +Jack: 7 +Doty: 7 +Bartley: 7 +Yazzie: 7 +Rowell: 7 +Parson: 7 +Gifford: 7 +Cullen: 7 +Christiansen: 7 +Benavides: 7 +Barnhart: 7 +Talbot: 7 +Mock: 7 +Crandall: 7 +Connors: 7 +Bonds: 7 +Whitt: 7 +Gage: 7 +Bergman: 7 +Arredondo: 7 +Addison: 7 +Marion: 7 +Lujan: 7 +Dowdy: 7 +Jernigan: 7 +Huynh: 7 +Bouchard: 7 +Dutton: 7 +Rhoades: 7 +Ouellette: 7 +Kiser: 7 +Rubin: 7 +Herrington: 7 +Hare: 7 +Denny: 7 +Blackman: 7 +Babb: 7 +Allred: 7 +Rudd: 7 +Paulson: 7 +Ogden: 7 +Koenig: 7 +Jacob: 7 +Irving: 7 +Geiger: 7 +Begay: 7 +Parra: 7 +Champion: 7 +Lassiter: 7 +Hawk: 7 +Esposito: 7 +Cho: 7 +Waldron: 7 +Vernon: 7 +Ransom: 7 +Prather: 7 +Keenan: 7 +Jean: 7 +Grover: 7 +Chacon: 7 +Vick: 7 +Sands: 7 +Roark: 7 +Parr: 7 +Mayberry: 7 +Greenberg: 7 +Coley: 7 +Bruner: 7 +Whitman: 7 +Skaggs: 7 +Shipman: 7 +Means: 7 +Leary: 7 +Hutton: 7 +Romo: 7 +Medrano: 7 +Ladd: 7 +Kruse: 7 +Friend: 7 +Darling: 7 +Askew: 7 +Valentin: 7 +Schulz: 7 +Alfaro: 7 +Tabor: 7 +Mohr: 7 +Gallo: 7 +Bermudez: 7 +Pereira: 7 +Isaac: 7 +Bliss: 7 +Reaves: 6 +Flint: 6 +Comer: 6 +Boston: 6 +Woodall: 6 +Naquin: 6 +Guevara: 6 +Earl: 6 +Delong: 6 +Carrier: 6 +Pickens: 6 +Brand: 6 +Tilley: 6 +Schaffer: 6 +Read: 6 +Lim: 6 +Knutson: 6 +Fenton: 6 +Doran: 6 +Chu: 6 +Vogt: 6 +Vann: 6 +Prescott: 6 +Mclain: 6 +Landis: 6 +Corcoran: 6 +Ambrose: 6 +Zapata: 6 +Hyatt: 6 +Hemphill: 6 +Faulk: 6 +Call: 6 +Dove: 6 +Boudreaux: 6 +Aragon: 6 +Whitlock: 6 +Trejo: 6 +Tackett: 6 +Shearer: 6 +Saldana: 6 +Hanks: 6 +Gold: 6 +Driver: 6 +Mckinnon: 6 +Koehler: 6 +Champagne: 6 +Bourgeois: 6 +Pool: 6 +Keyes: 6 +Goodson: 6 +Foote: 6 +Early: 6 +Lunsford: 6 +Goldsmith: 6 +Flood: 6 +Winslow: 6 +Sams: 6 +Reagan: 6 +Mccloud: 6 +Hough: 6 +Esquivel: 6 +Naylor: 6 +Loomis: 6 +Coronado: 6 +Ludwig: 6 +Braswell: 6 +Bearden: 6 +Sherrill: 6 +Huang: 6 +Fagan: 6 +Ezell: 6 +Edmondson: 6 +Cyr: 6 +Cronin: 6 +Nunn: 6 +Lemon: 6 +Guillory: 6 +Grier: 6 +Dubose: 6 +Traylor: 6 +Ryder: 6 +Dobbins: 6 +Coyle: 6 +Aponte: 6 +Whitmore: 6 +Smalls: 6 +Rowan: 6 +Malloy: 6 +Cardona: 6 +Braxton: 6 +Borden: 6 +Humphries: 6 +Carrasco: 6 +Ruff: 6 +Metzger: 6 +Huntley: 6 +Hinojosa: 6 +Finney: 6 +Madsen: 6 +Hong: 6 +Hills: 6 +Ernst: 6 +Dozier: 6 +Burkhart: 6 +Bowser: 6 +Peralta: 6 +Daigle: 6 +Whittington: 6 +Sorenson: 6 +Saucedo: 6 +Roche: 6 +Redding: 6 +Loyd: 6 +Fugate: 6 +Avalos: 6 +Waite: 6 +Lind: 6 +Huston: 6 +Hay: 6 +Benedict: 6 +Hawthorne: 6 +Hamby: 6 +Boyles: 6 +Boles: 6 +Regan: 6 +Faust: 6 +Crook: 6 +Beam: 6 +Barger: 6 +Hinds: 6 +Gallardo: 6 +Elias: 6 +Willoughby: 6 +Willingham: 6 +Wilburn: 6 +Eckert: 6 +Busch: 6 +Zepeda: 6 +Worthington: 6 +Tinsley: 6 +Russ: 6 +Li: 6 +Hoff: 6 +Hawley: 6 +Carmona: 6 +Varela: 6 +Rector: 6 +Newcomb: 6 +Mallory: 6 +Kinsey: 6 +Dube: 6 +Whatley: 6 +Strange: 6 +Ragsdale: 6 +Ivy: 6 +Bernstein: 6 +Becerra: 6 +Yost: 6 +Mattson: 6 +Ly: 6 +Felder: 6 +Cheek: 6 +Luke: 6 +Handy: 6 +Grossman: 6 +Gauthier: 6 +Escobedo: 6 +Braden: 6 +Beckman: 6 +Mott: 6 +Hillman: 6 +Gil: 6 +Flaherty: 6 +Dykes: 6 +Doe: 6 +Stockton: 6 +Stearns: 6 +Lofton: 6 +Kitchen: 6 +Coats: 6 +Cavazos: 6 +Beavers: 6 +Barrios: 6 +Tang: 6 +Parish: 6 +Mosher: 6 +Lincoln: 6 +Cardwell: 6 +Coles: 6 +Burnham: 6 +Weller: 6 +Lemons: 6 +Beebe: 6 +Aguilera: 6 +Ring: 6 +Parnell: 6 +Harman: 6 +Couture: 6 +Alley: 6 +Schumacher: 6 +Redd: 6 +Dobbs: 6 +Blum: 6 +Blalock: 6 +Merchant: 6 +Ennis: 6 +Denson: 6 +Cottrell: 6 +Chester: 6 +Brannon: 6 +Bagley: 6 +Aviles: 6 +Watt: 6 +Sousa: 6 +Rosenthal: 6 +Rooney: 6 +Dietz: 6 +Blank: 6 +Paquette: 6 +Mcclelland: 6 +Duff: 6 +Velasco: 6 +Lentz: 6 +Grubb: 6 +Burrows: 6 +Barbour: 6 +Ulrich: 6 +Shockley: 6 +Rader: 6 +German: 6 +Beyer: 6 +Mixon: 6 +Layton: 6 +Altman: 6 +Alonzo: 6 +Weathers: 6 +Titus: 6 +Stoner: 6 +Squires: 6 +Shipp: 6 +Priest: 6 +Lipscomb: 6 +Cutler: 6 +Caballero: 6 +Zimmer: 6 +Willett: 6 +Thurston: 6 +Storey: 6 +Medley: 6 +Lyle: 6 +Epperson: 6 +Shah: 6 +Mcmillian: 6 +Baggett: 6 +Torrez: 6 +Laws: 6 +Hirsch: 6 +Dent: 6 +Corey: 6 +Poirier: 6 +Peachey: 6 +Jacques: 6 +Farrar: 6 +Creech: 6 +Barth: 6 +Trimble: 6 +France: 6 +Dupre: 6 +Albrecht: 6 +Sample: 6 +Lawler: 6 +Crisp: 6 +Conroy: 6 +Chadwick: 6 +Wetzel: 6 +Nesbitt: 6 +Murry: 6 +Jameson: 6 +Wilhelm: 6 +Patten: 6 +Minton: 6 +Matson: 6 +Kimbrough: 6 +Iverson: 6 +Guinn: 6 +Gale: 6 +Fortune: 6 +Croft: 6 +Toth: 6 +Pulliam: 6 +Nugent: 6 +Newby: 6 +Littlejohn: 6 +Dias: 6 +Canales: 6 +Bernier: 6 +Baron: 6 +Barney: 6 +Singletary: 6 +Renteria: 6 +Pruett: 6 +Mchugh: 6 +Mabry: 6 +Landrum: 6 +Brower: 6 +Weldon: 6 +Stoddard: 6 +Ruth: 6 +Cagle: 6 +Stjohn: 6 +Scales: 6 +Kohler: 6 +Kellogg: 6 +Hopson: 6 +Gant: 6 +Tharp: 6 +Gann: 6 +Zeigler: 6 +Pringle: 6 +Hammons: 6 +Fairchild: 6 +Deaton: 6 +Chavis: 6 +Carnes: 6 +Rowley: 6 +Matlock: 6 +Libby: 6 +Kearns: 6 +Irizarry: 6 +Carrington: 6 +Starkey: 6 +Pepper: 6 +Lopes: 6 +Jarrell: 6 +Fay: 6 +Craven: 6 +Beverly: 6 +Baum: 6 +Spain: 5 +Littlefield: 5 +Linn: 5 +Humphreys: 5 +Hook: 5 +High: 5 +Etheridge: 5 +Cuellar: 5 +Chastain: 5 +Chance: 5 +Bundy: 5 +Speer: 5 +Skelton: 5 +Quiroz: 5 +Pyle: 5 +Portillo: 5 +Ponder: 5 +Moulton: 5 +Machado: 5 +Liu: 5 +Killian: 5 +Hutson: 5 +Hitchcock: 5 +Ellsworth: 5 +Dowling: 5 +Cloud: 5 +Burdick: 5 +Spann: 5 +Pedersen: 5 +Levin: 5 +Leggett: 5 +Hayward: 5 +Hacker: 5 +Dietrich: 5 +Beaulieu: 5 +Barksdale: 5 +Wakefield: 5 +Snowden: 5 +Paris: 5 +Briscoe: 5 +Bowie: 5 +Berman: 5 +Ogle: 5 +Mcgregor: 5 +Laughlin: 5 +Helm: 5 +Burden: 5 +Wheatley: 5 +Schreiber: 5 +Pressley: 5 +Parris: 5 +Ng: 5 +Alaniz: 5 +Agee: 5 +Urban: 5 +Swann: 5 +Snodgrass: 5 +Schuster: 5 +Radford: 5 +Monk: 5 +Mattingly: 5 +Main: 5 +Lamar: 5 +Harp: 5 +Girard: 5 +Cheney: 5 +Yancey: 5 +Wagoner: 5 +Ridley: 5 +Lombardo: 5 +Lau: 5 +Hudgins: 5 +Gaskins: 5 +Duckworth: 5 +Coe: 5 +Coburn: 5 +Willey: 5 +Prado: 5 +Newberry: 5 +Magana: 5 +Hammonds: 5 +Elam: 5 +Whipple: 5 +Slade: 5 +Serna: 5 +Ojeda: 5 +Liles: 5 +Dorman: 5 +Diehl: 5 +Angel: 5 +Upton: 5 +Reardon: 5 +Michaels: 5 +Kelsey: 5 +Goetz: 5 +Eller: 5 +Bauman: 5 +Baer: 5 +Augustine: 5 +Layne: 5 +Hummel: 5 +Brenner: 5 +Amaya: 5 +Adamson: 5 +Ornelas: 5 +Dowell: 5 +Cloutier: 5 +Christy: 5 +Castellanos: 5 +Wing: 5 +Wellman: 5 +Saylor: 5 +Orourke: 5 +Moya: 5 +Montalvo: 5 +Kilpatrick: 5 +Harley: 5 +Durbin: 5 +Shell: 5 +Oldham: 5 +Kang: 5 +Garvin: 5 +Foss: 5 +Branham: 5 +Bartholomew: 5 +Templeton: 5 +Maguire: 5 +Holton: 5 +Alonso: 5 +Rider: 5 +Monahan: 5 +Mccormack: 5 +Beaty: 5 +Anders: 5 +Streeter: 5 +Nieto: 5 +Nielson: 5 +Moffett: 5 +Lankford: 5 +Keating: 5 +Heck: 5 +Gatlin: 5 +Delatorre: 5 +Callaway: 5 +Adcock: 5 +Worrell: 5 +Unger: 5 +Robinette: 5 +Nowak: 5 +Jeter: 5 +Brunner: 5 +Ashton: 5 +Steen: 5 +Parrott: 5 +Overstreet: 5 +Nobles: 5 +Montanez: 5 +Luther: 5 +Clevenger: 5 +Brinkley: 5 +Trahan: 5 +Quarles: 5 +Pickering: 5 +Pederson: 5 +Jansen: 5 +Grantham: 5 +Gilchrist: 5 +Crespo: 5 +Aiken: 5 +Schell: 5 +Schaeffer: 5 +Lorenz: 5 +Leyva: 5 +Harms: 5 +Dyson: 5 +Wallis: 5 +Pease: 5 +Leavitt: 5 +Hyman: 5 +Cheng: 5 +Cavanaugh: 5 +Batts: 5 +Warden: 5 +Seaman: 5 +Rockwell: 5 +Quezada: 5 +Paxton: 5 +Linder: 5 +Houck: 5 +Fontaine: 5 +Durant: 5 +Caruso: 5 +Adler: 5 +Pimentel: 5 +Mize: 5 +Lytle: 5 +Donald: 5 +Cleary: 5 +Cason: 5 +Acker: 5 +Switzer: 5 +Salmon: 5 +Isaacs: 5 +Higginbotham: 5 +Han: 5 +Waterman: 5 +Vandyke: 5 +Stamper: 5 +Sisk: 5 +Shuler: 5 +Riddick: 5 +Redman: 5 +Mcmahan: 5 +Levesque: 5 +Hatton: 5 +Bronson: 5 +Bollinger: 5 +Arnett: 5 +Okeefe: 5 +Gerber: 5 +Gannon: 5 +Farnsworth: 5 +Baughman: 5 +Silverman: 5 +Satterfield: 5 +Royal: 5 +Mccrary: 5 +Kowalski: 5 +Joy: 5 +Grigsby: 5 +Greco: 5 +Cabral: 5 +Trout: 5 +Rinehart: 5 +Mahon: 5 +Linton: 5 +Gooden: 5 +Curley: 5 +Baugh: 5 +Wyman: 5 +Weiner: 5 +Schwab: 5 +Schuler: 5 +Morrissey: 5 +Mahan: 5 +Coy: 5 +Bunn: 5 +Andrew: 5 +Thrasher: 5 +Spear: 5 +Waggoner: 5 +Shelley: 5 +Robert: 5 +Qualls: 5 +Purdy: 5 +Mcwhorter: 5 +Mauldin: 5 +Mark: 5 +Jordon: 5 +Gilman: 5 +Perryman: 5 +Newsom: 5 +Menard: 5 +Martino: 5 +Graf: 5 +Billingsley: 5 +Artis: 5 +Simpkins: 5 +Salisbury: 5 +Quintanilla: 5 +Gilliland: 5 +Fraley: 5 +Foust: 5 +Crouse: 5 +Scarborough: 5 +Ngo: 5 +Grissom: 5 +Fultz: 5 +Rico: 5 +Marlow: 5 +Markham: 5 +Madrigal: 5 +Lawton: 5 +Barfield: 5 +Whiting: 5 +Varney: 5 +Schwarz: 5 +Huey: 5 +Gooch: 5 +Arce: 5 +Wheat: 5 +Truong: 5 +Poulin: 5 +Mackenzie: 5 +Leone: 5 +Hurtado: 5 +Selby: 5 +Gaither: 5 +Fortner: 5 +Culpepper: 5 +Coughlin: 5 +Brinson: 5 +Boudreau: 5 +Barkley: 5 +Bales: 5 +Stepp: 5 +Holm: 5 +Tan: 5 +Schilling: 5 +Morrell: 5 +Kahn: 5 +Heaton: 5 +Gamez: 5 +Douglass: 5 +Causey: 5 +Brothers: 5 +Turpin: 5 +Shanks: 5 +Schrader: 5 +Meek: 5 +Isom: 5 +Hardison: 5 +Carranza: 5 +Yanez: 5 +Way: 5 +Scroggins: 5 +Schofield: 5 +Runyon: 5 +Ratcliff: 5 +Murrell: 5 +Moeller: 5 +Irby: 5 +Currier: 5 +Butterfield: 5 +Yee: 5 +Ralston: 5 +Pullen: 5 +Pinson: 5 +Estep: 5 +East: 5 +Carbone: 5 +Lance: 5 +Hawks: 5 +Ellington: 5 +Casillas: 5 +Spurlock: 5 +Sikes: 5 +Motley: 5 +Mccartney: 5 +Kruger: 5 +Isbell: 5 +Houle: 5 +Francisco: 5 +Burk: 5 +Bone: 5 +Tomlin: 5 +Shelby: 5 +Quigley: 5 +Neumann: 5 +Lovelace: 5 +Fennell: 5 +Colby: 5 +Cheatham: 5 +Bustamante: 5 +Skidmore: 5 +Hidalgo: 5 +Forman: 5 +Culp: 5 +Bowens: 5 +Betancourt: 5 +Aquino: 5 +Robb: 5 +Rea: 5 +Milner: 5 +Martel: 5 +Gresham: 5 +Wiles: 5 +Ricketts: 5 +Gavin: 5 +Dowd: 5 +Collazo: 5 +Bostic: 5 +Blakely: 5 +Sherrod: 5 +Power: 5 +Kenyon: 5 +Gandy: 5 +Ebert: 5 +Deloach: 5 +Cary: 5 +Bull: 5 +Allard: 5 +Sauer: 5 +Robins: 5 +Olivares: 5 +Gillette: 5 +Chestnut: 5 +Bourque: 5 +Paine: 5 +Lyman: 5 +Hite: 5 +Hauser: 5 +Devore: 5 +Crawley: 5 +Chapa: 5 +Vu: 5 +Tobias: 5 +Talbert: 5 +Poindexter: 5 +Millard: 5 +Meador: 5 +Mcduffie: 5 +Mattox: 5 +Kraus: 5 +Harkins: 5 +Choate: 5 +Bess: 5 +Wren: 5 +Sledge: 5 +Sanborn: 5 +Outlaw: 5 +Kinder: 5 +Geary: 5 +Cornwell: 5 +Barclay: 5 +Adam: 5 +Abney: 5 +Seward: 5 +Rhoads: 5 +Howland: 5 +Fortier: 5 +Easter: 5 +Benner: 5 +Vines: 5 +Tubbs: 5 +Troutman: 5 +Rapp: 5 +Noe: 5 +Mccurdy: 5 +Harder: 5 +Deluca: 5 +Westmoreland: 5 +South: 5 +Havens: 5 +Guajardo: 5 +Ely: 5 +Clary: 5 +Seal: 5 +Meehan: 5 +Herzog: 5 +Guillen: 5 +Ashcraft: 5 +Waugh: 5 +Renner: 5 +Milam: 5 +Jung: 5 +Elrod: 5 +Churchill: 5 +Buford: 5 +Breaux: 5 +Bolin: 5 +Asher: 5 +Windham: 5 +Tirado: 5 +Pemberton: 5 +Nolen: 5 +Noland: 5 +Knott: 5 +Emmons: 5 +Cornish: 5 +Christenson: 5 +Brownlee: 5 +Barbee: 5 +Waldrop: 4 +Pitt: 4 +Olvera: 4 +Lombardi: 4 +Gruber: 4 +Gaffney: 4 +Eggleston: 4 +Banda: 4 +Archuleta: 4 +Still: 4 +Slone: 4 +Prewitt: 4 +Pfeiffer: 4 +Nettles: 4 +Mena: 4 +Mcadams: 4 +Henning: 4 +Gardiner: 4 +Cromwell: 4 +Chisholm: 4 +Burleson: 4 +Box: 4 +Vest: 4 +Oglesby: 4 +Mccarter: 4 +Malcolm: 4 +Lumpkin: 4 +Larue: 4 +Grey: 4 +Wofford: 4 +Vanhorn: 4 +Thorn: 4 +Teel: 4 +Swafford: 4 +Stclair: 4 +Stanfield: 4 +Ocampo: 4 +Herrmann: 4 +Hannon: 4 +Arsenault: 4 +Roush: 4 +Mcalister: 4 +Hiatt: 4 +Gunderson: 4 +Forsythe: 4 +Duggan: 4 +Delvalle: 4 +Cintron: 4 +Wilks: 4 +Weinstein: 4 +Uribe: 4 +Rizzo: 4 +Noyes: 4 +Mclendon: 4 +Gurley: 4 +Bethea: 4 +Winstead: 4 +Maples: 4 +Harry: 4 +Guyton: 4 +Giordano: 4 +Alderman: 4 +Valdes: 4 +Polanco: 4 +Pappas: 4 +Lively: 4 +Grogan: 4 +Griffiths: 4 +Bobo: 4 +Arevalo: 4 +Whitson: 4 +Sowell: 4 +Rendon: 4 +Matthew: 4 +Julian: 4 +Fernandes: 4 +Farrow: 4 +Edmond: 4 +Benavidez: 4 +Ayres: 4 +Alicea: 4 +Stump: 4 +Smalley: 4 +Seitz: 4 +Schulte: 4 +Gilley: 4 +Gallant: 4 +Dewey: 4 +Casper: 4 +Canfield: 4 +Wolford: 4 +Omalley: 4 +Mcnutt: 4 +Mcnulty: 4 +Mcgovern: 4 +Hardman: 4 +Harbin: 4 +Cowart: 4 +Chavarria: 4 +Brink: 4 +Beckett: 4 +Bagwell: 4 +Armstead: 4 +Anglin: 4 +Abreu: 4 +Reynoso: 4 +Krebs: 4 +Jett: 4 +Hoffmann: 4 +Greenfield: 4 +Forte: 4 +Burney: 4 +Broome: 4 +Sisson: 4 +Parent: 4 +Jude: 4 +Younger: 4 +Trammell: 4 +Partridge: 4 +Marvin: 4 +Mace: 4 +Lomax: 4 +Lemieux: 4 +Gossett: 4 +Frantz: 4 +Fogle: 4 +Cooney: 4 +Broughton: 4 +Pence: 4 +Paulsen: 4 +Neil: 4 +Muncy: 4 +Mcarthur: 4 +Hollins: 4 +Edward: 4 +Beauchamp: 4 +Withers: 4 +Osorio: 4 +Mulligan: 4 +Hoyle: 4 +Foy: 4 +Dockery: 4 +Cockrell: 4 +Begley: 4 +Amador: 4 +Roby: 4 +Rains: 4 +Lindquist: 4 +Gentile: 4 +Everhart: 4 +Bohannon: 4 +Wylie: 4 +Thao: 4 +Sommers: 4 +Purnell: 4 +Palma: 4 +Fortin: 4 +Dunning: 4 +Breeden: 4 +Vail: 4 +Phelan: 4 +Phan: 4 +Marx: 4 +Cosby: 4 +Colburn: 4 +Chong: 4 +Boling: 4 +Biddle: 4 +Ledesma: 4 +Gaddis: 4 +Denney: 4 +Chow: 4 +Bueno: 4 +Berrios: 4 +Wicker: 4 +Tolliver: 4 +Thibodeaux: 4 +Nagle: 4 +Lavoie: 4 +Fisk: 4 +Do: 4 +Crist: 4 +Barbosa: 4 +Reedy: 4 +March: 4 +Locklear: 4 +Kolb: 4 +Himes: 4 +Behrens: 4 +Beckwith: 4 +Beckham: 4 +Weems: 4 +Wahl: 4 +Shorter: 4 +Shackelford: 4 +Rees: 4 +Muse: 4 +Free: 4 +Cerda: 4 +Valadez: 4 +Thibodeau: 4 +Saavedra: 4 +Ridgeway: 4 +Reiter: 4 +Mchenry: 4 +Majors: 4 +Lachance: 4 +Keaton: 4 +Israel: 4 +Ferrara: 4 +Falcon: 4 +Clemens: 4 +Blocker: 4 +Applegate: 4 +Paz: 4 +Needham: 4 +Mojica: 4 +Kuykendall: 4 +Hamel: 4 +Escamilla: 4 +Doughty: 4 +Burchett: 4 +Ainsworth: 4 +Wilbur: 4 +Vidal: 4 +Upchurch: 4 +Thigpen: 4 +Strauss: 4 +Spruill: 4 +Sowers: 4 +Riggins: 4 +Ricker: 4 +Mccombs: 4 +Harlow: 4 +Garnett: 4 +Buffington: 4 +Yi: 4 +Sotelo: 4 +Olivas: 4 +Negrete: 4 +Morey: 4 +Macon: 4 +Logsdon: 4 +Lapointe: 4 +Florence: 4 +Cathey: 4 +Bigelow: 4 +Bello: 4 +Westfall: 4 +Stubblefield: 4 +Peak: 4 +Lindley: 4 +Jeffrey: 4 +Hein: 4 +Hawes: 4 +Farrington: 4 +Edge: 4 +Breen: 4 +Birch: 4 +Wilde: 4 +Steed: 4 +Sepulveda: 4 +Reinhardt: 4 +Proffitt: 4 +Minter: 4 +Messina: 4 +Mcnabb: 4 +Maier: 4 +Keeler: 4 +Gamboa: 4 +Donohue: 4 +Dexter: 4 +Basham: 4 +Shinn: 4 +Orlando: 4 +Crooks: 4 +Cota: 4 +Borders: 4 +Bills: 4 +Bachman: 4 +Tisdale: 4 +Tavares: 4 +Schmid: 4 +Pickard: 4 +Jasper: 4 +Gulley: 4 +Fonseca: 4 +Delossantos: 4 +Condon: 4 +Clancy: 4 +Batista: 4 +Wicks: 4 +Wadsworth: 4 +New: 4 +Martell: 4 +Lo: 4 +Littleton: 4 +Ison: 4 +Haag: 4 +Folsom: 4 +Brumfield: 4 +Broyles: 4 +Brito: 4 +Mireles: 4 +Mcdonnell: 4 +Leclair: 4 +Hamblin: 4 +Gough: 4 +Fanning: 4 +Binder: 4 +Winfield: 4 +Whitworth: 4 +Soriano: 4 +Palumbo: 4 +Newkirk: 4 +Mangum: 4 +Hutcherson: 4 +Comstock: 4 +Cecil: 4 +Carlin: 4 +Beall: 4 +Bair: 4 +Wendt: 4 +Watters: 4 +Walling: 4 +Putman: 4 +Otoole: 4 +Oliva: 4 +Morley: 4 +Mares: 4 +Lemus: 4 +Keener: 4 +Jeffery: 4 +Hundley: 4 +Dial: 4 +Damico: 4 +Billups: 4 +Strother: 4 +Mcfarlane: 4 +Lamm: 4 +Eaves: 4 +Crutcher: 4 +Caraballo: 4 +Canty: 4 +Atwell: 4 +Taft: 4 +Siler: 4 +Rust: 4 +Rawls: 4 +Rawlings: 4 +Prieto: 4 +Niles: 4 +Mcneely: 4 +Mcafee: 4 +Hulsey: 4 +Harlan: 4 +Hackney: 4 +Galvez: 4 +Escalante: 4 +Delagarza: 4 +Crider: 4 +Charlton: 4 +Bandy: 4 +Wilbanks: 4 +Stowe: 4 +Steinberg: 4 +Samson: 4 +Renfro: 4 +Masterson: 4 +Massie: 4 +Lanham: 4 +Haskell: 4 +Hamrick: 4 +Fort: 4 +Dehart: 4 +Card: 4 +Burdette: 4 +Branson: 4 +Bourne: 4 +Babin: 4 +Aleman: 4 +Worthy: 4 +Tibbs: 4 +Sweat: 4 +Smoot: 4 +Slack: 4 +Paradis: 4 +Packard: 4 +Mull: 4 +Luce: 4 +Houghton: 4 +Gantt: 4 +Furman: 4 +Danner: 4 +Christianson: 4 +Burge: 4 +Broderick: 4 +Ashford: 4 +Arndt: 4 +Almeida: 4 +Stallworth: 4 +Shade: 4 +Searcy: 4 +Sager: 4 +Noonan: 4 +Mclemore: 4 +Mcintire: 4 +Maxey: 4 +Lavigne: 4 +Jobe: 4 +Ireland: 4 +Ferrer: 4 +Falk: 4 +Edgar: 4 +Coffin: 4 +Byrnes: 4 +Aranda: 4 +Apodaca: 4 +Stamps: 4 +Rounds: 4 +Peek: 4 +Olmstead: 4 +Lewandowski: 4 +Kaminski: 4 +Her: 4 +Dunaway: 4 +Bruns: 4 +Brackett: 4 +Amato: 4 +Reich: 4 +Mcclung: 4 +Lacroix: 4 +Koontz: 4 +Herrick: 4 +Hardesty: 4 +Flanders: 4 +Cousins: 4 +Close: 4 +Cato: 4 +Cade: 4 +Vickery: 4 +Shank: 4 +Nagel: 4 +Dupuis: 4 +Croteau: 4 +Cotter: 4 +Cable: 4 +Stuckey: 4 +Stine: 4 +Porterfield: 4 +Pauley: 4 +Nye: 4 +Moffitt: 4 +Lu: 4 +Knudsen: 4 +Hardwick: 4 +Goforth: 4 +Dupont: 4 +Blunt: 4 +Barrows: 4 +Barnhill: 4 +Shull: 4 +Rash: 4 +Ralph: 4 +Penny: 4 +Lorenzo: 4 +Loftis: 4 +Lemay: 4 +Kitchens: 4 +Horvath: 4 +Grenier: 4 +Fuchs: 4 +Fairbanks: 4 +Culbertson: 4 +Calkins: 4 +Burnside: 4 +Beattie: 4 +Ashworth: 4 +Albertson: 4 +Wertz: 4 +Vo: 4 +Vaught: 4 +Vallejo: 4 +Tyree: 4 +Turk: 4 +Tuck: 4 +Tijerina: 4 +Sage: 4 +Picard: 4 +Peterman: 4 +Otis: 4 +Marroquin: 4 +Marr: 4 +Lantz: 4 +Hoang: 4 +Demarco: 4 +Daily: 4 +Cone: 4 +Berube: 4 +Barnette: 4 +Wharton: 4 +Stinnett: 4 +Slocum: 4 +Scanlon: 4 +Sander: 4 +Pinto: 4 +Mancuso: 4 +Lima: 4 +Judge: 4 +Headley: 4 +Epstein: 4 +Counts: 4 +Clarkson: 4 +Carnahan: 4 +Brice: 4 +Boren: 4 +Arteaga: 4 +Adame: 4 +Zook: 4 +Whittle: 4 +Whitehurst: 4 +Wenzel: 4 +Saxton: 4 +Rhea: 4 +Reddick: 4 +Puente: 4 +Hazel: 4 +Handley: 4 +Haggerty: 4 +Earley: 4 +Devlin: 4 +Dallas: 4 +Chaffin: 4 +Cady: 4 +Ahmed: 4 +Acuna: 4 +Solano: 4 +Sigler: 4 +Pollack: 4 +Pendergrass: 4 +Ostrander: 4 +Janes: 4 +Francois: 4 +Fine: 4 +Crutchfield: 4 +Cordell: 4 +Chamberlin: 4 +Brubaker: 4 +Baptiste: 4 +Willson: 4 +Reis: 4 +Neeley: 4 +Mullin: 4 +Mercier: 4 +Lira: 4 +Layman: 4 +Keeling: 4 +Higdon: 4 +Guest: 4 +Forrester: 4 +Espinal: 4 +Dion: 4 +Chapin: 4 +Carl: 4 +Warfield: 4 +Toledo: 4 +Pulido: 4 +Peebles: 4 +Nagy: 4 +Montague: 4 +Mello: 4 +Lear: 4 +Jaeger: 4 +Hogg: 4 +Graff: 4 +Furr: 4 +Derrick: 4 +Cave: 4 +Canada: 4 +Soliz: 4 +Poore: 4 +Mendenhall: 4 +Mclaurin: 4 +Maestas: 4 +Low: 4 +Gable: 4 +Belt: 4 +Barraza: 4 +Tillery: 4 +Snead: 4 +Pond: 4 +Neill: 4 +Mcculloch: 4 +Mccorkle: 4 +Lightfoot: 4 +Hutchings: 4 +Holloman: 4 +Harness: 4 +Dorn: 4 +Council: 4 +Bock: 4 +Zielinski: 4 +Turley: 4 +Treadwell: 4 +Stpierre: 4 +Starling: 4 +Somers: 4 +Oswald: 4 +Merrick: 4 +Marquis: 4 +Ivory: 4 +Easterling: 4 +Bivens: 4 +Truitt: 4 +Poston: 4 +Parry: 4 +Ontiveros: 4 +Olivarez: 4 +Neville: 4 +Moreau: 4 +Medlin: 4 +Ma: 4 +Lenz: 4 +Knowlton: 4 +Fairley: 4 +Cobbs: 4 +Chisolm: 4 +Bannister: 4 +Woodworth: 4 +Toler: 4 +Ocasio: 4 +Noriega: 4 +Neuman: 4 +Moye: 4 +Milburn: 4 +Mcclanahan: 4 +Lilley: 4 +Hanes: 4 +Flannery: 4 +Dellinger: 4 +Danielson: 4 +Conti: 4 +Blodgett: 4 +Beers: 4 +Weatherford: 4 +Strain: 4 +Karr: 4 +Hitt: 4 +Denham: 4 +Custer: 4 +Coble: 4 +Clough: 4 +Casteel: 4 +Bolduc: 4 +Batchelor: 4 +Ammons: 4 +Whitlow: 4 +Tierney: 4 +Staten: 4 +Sibley: 4 +Seifert: 4 +Schubert: 4 +Salcedo: 4 +Mattison: 4 +Laney: 4 +Haggard: 4 +Grooms: 4 +Dix: 4 +Dees: 4 +Cromer: 4 +Cooks: 4 +Colson: 4 +Caswell: 4 +Zarate: 4 +Swisher: 4 +Stacey: 4 +Shin: 4 +Ragan: 4 +Pridgen: 4 +Mcvey: 4 +Matheny: 4 +Leigh: 4 +Lafleur: 4 +Franz: 4 +Ferraro: 4 +Dugger: 4 +Whiteside: 4 +Rigsby: 4 +Mcmurray: 4 +Lehmann: 4 +Large: 4 +Jacoby: 4 +Hildebrand: 4 +Hendrick: 4 +Headrick: 4 +Goad: 4 +Fincher: 4 +Drury: 4 +Borges: 4 +Archibald: 4 +Albers: 4 +Woodcock: 4 +Trapp: 4 +Soares: 4 +Seaton: 4 +Richie: 4 +Monson: 4 +Luckett: 4 +Lindberg: 4 +Kopp: 4 +Keeton: 4 +Hsu: 4 +Healey: 4 +Garvey: 4 +Gaddy: 4 +Fain: 4 +Burchfield: 4 +Badger: 4 +Wentworth: 4 +Strand: 4 +Stack: 4 +Spooner: 4 +Saucier: 4 +Sales: 4 +Ruby: 4 +Ricci: 4 +Plunkett: 4 +Pannell: 4 +Ness: 4 +Leger: 4 +Hoy: 4 +Freitas: 4 +Fong: 4 +Elizondo: 4 +Duval: 4 +Chun: 4 +Calvin: 4 +Beaudoin: 4 +Urbina: 4 +Stock: 4 +Rickard: 4 +Partin: 4 +Moe: 4 +Mcgrew: 4 +Mcclintock: 4 +Ledoux: 4 +Forsyth: 4 +Faison: 4 +Devries: 4 +Bertrand: 4 +Wasson: 3 +Tilton: 3 +Scarbrough: 3 +Pride: 3 +Oh: 3 +Leung: 3 +Larry: 3 +Irvine: 3 +Garber: 3 +Denning: 3 +Corral: 3 +Colley: 3 +Castleberry: 3 +Bowlin: 3 +Bogan: 3 +Beale: 3 +Baines: 3 +True: 3 +Trice: 3 +Rayburn: 3 +Parkinson: 3 +Pak: 3 +Nunes: 3 +Mcmillen: 3 +Leahy: 3 +Lea: 3 +Kimmel: 3 +Higgs: 3 +Fulmer: 3 +Carden: 3 +Bedford: 3 +Taggart: 3 +Spearman: 3 +Register: 3 +Prichard: 3 +Morrill: 3 +Koonce: 3 +Heinz: 3 +Hedges: 3 +Guenther: 3 +Grice: 3 +Findley: 3 +Earle: 3 +Dover: 3 +Creighton: 3 +Boothe: 3 +Bayer: 3 +Arreola: 3 +Vitale: 3 +Valles: 3 +See: 3 +Raney: 3 +Peter: 3 +Osgood: 3 +Lowell: 3 +Hanlon: 3 +Burley: 3 +Bounds: 3 +Worden: 3 +Weatherly: 3 +Vetter: 3 +Tanaka: 3 +Stiltner: 3 +Sell: 3 +Nevarez: 3 +Mosby: 3 +Montero: 3 +Melancon: 3 +Harter: 3 +Hamer: 3 +Goble: 3 +Gladden: 3 +Gist: 3 +Ginn: 3 +Akin: 3 +Zaragoza: 3 +Towns: 3 +Tarver: 3 +Sammons: 3 +Royster: 3 +Oreilly: 3 +Muir: 3 +Morehead: 3 +Luster: 3 +Kingsley: 3 +Kelso: 3 +Grisham: 3 +Glynn: 3 +Baumann: 3 +Alves: 3 +Yount: 3 +Tamayo: 3 +Tam: 3 +Paterson: 3 +Oates: 3 +Menendez: 3 +Longo: 3 +Hargis: 3 +Greenlee: 3 +Gillen: 3 +Desantis: 3 +Conover: 3 +Breedlove: 3 +Wayne: 3 +Sumpter: 3 +Scherer: 3 +Rupp: 3 +Reichert: 3 +Heredia: 3 +Fallon: 3 +Creel: 3 +Cohn: 3 +Clemmons: 3 +Casas: 3 +Bickford: 3 +Belton: 3 +Bach: 3 +Williford: 3 +Whitcomb: 3 +Tennant: 3 +Sutter: 3 +Stull: 3 +Sessions: 3 +Mccallum: 3 +Manson: 3 +Langlois: 3 +Keel: 3 +Keegan: 3 +Emanuel: 3 +Dangelo: 3 +Dancy: 3 +Damron: 3 +Clapp: 3 +Clanton: 3 +Bankston: 3 +Trinidad: 3 +Oliveira: 3 +Mintz: 3 +Mcinnis: 3 +Martens: 3 +Mabe: 3 +Laster: 3 +Jolley: 3 +Irish: 3 +Hildreth: 3 +Hefner: 3 +Glaser: 3 +Duckett: 3 +Demers: 3 +Brockman: 3 +Blais: 3 +Back: 3 +Alcorn: 3 +Agnew: 3 +Toliver: 3 +Tice: 3 +Song: 3 +Seeley: 3 +Najera: 3 +Musser: 3 +Mcfall: 3 +Laplante: 3 +Galvin: 3 +Fajardo: 3 +Doan: 3 +Coyne: 3 +Copley: 3 +Clawson: 3 +Cheung: 3 +Barone: 3 +Wynne: 3 +Woodley: 3 +Tremblay: 3 +Stoll: 3 +Sparrow: 3 +Sparkman: 3 +Schweitzer: 3 +Sasser: 3 +Samples: 3 +Roney: 3 +Ramon: 3 +Legg: 3 +Lai: 3 +Joe: 3 +Heim: 3 +Farias: 3 +Concepcion: 3 +Colwell: 3 +Christman: 3 +Bratcher: 3 +Alba: 3 +Winchester: 3 +Upshaw: 3 +Southerland: 3 +Sorrell: 3 +Shay: 3 +Sells: 3 +Mount: 3 +Mccloskey: 3 +Martindale: 3 +Luttrell: 3 +Loveless: 3 +Lovejoy: 3 +Linares: 3 +Latimer: 3 +Holly: 3 +Embry: 3 +Coombs: 3 +Bratton: 3 +Bostick: 3 +Boss: 3 +Venable: 3 +Tuggle: 3 +Toro: 3 +Staggs: 3 +Sandlin: 3 +Jefferies: 3 +Heckman: 3 +Griffis: 3 +Crayton: 3 +Clem: 3 +Button: 3 +Browder: 3 +Allan: 3 +Thorton: 3 +Sturgill: 3 +Sprouse: 3 +Royer: 3 +Rousseau: 3 +Ridenour: 3 +Pogue: 3 +Perales: 3 +Peeples: 3 +Metzler: 3 +Mesa: 3 +Mccutcheon: 3 +Mcbee: 3 +Jay: 3 +Hornsby: 3 +Heffner: 3 +Corrigan: 3 +Armijo: 3 +Vue: 3 +Romeo: 3 +Plante: 3 +Peyton: 3 +Paredes: 3 +Macklin: 3 +Hussey: 3 +Hodgson: 3 +Granados: 3 +Frias: 3 +Carman: 3 +Brent: 3 +Becnel: 3 +Batten: 3 +Almanza: 3 +Turney: 3 +Teal: 3 +Sturgeon: 3 +Meeker: 3 +Mcdaniels: 3 +Limon: 3 +Keeney: 3 +Kee: 3 +Hutto: 3 +Holguin: 3 +Gorham: 3 +Fishman: 3 +Fierro: 3 +Blanchette: 3 +Rodrigue: 3 +Reddy: 3 +Osburn: 3 +Oden: 3 +Lerma: 3 +Kirkwood: 3 +Keefer: 3 +Haugen: 3 +Hammett: 3 +Chalmers: 3 +Carlos: 3 +Brinkman: 3 +Baumgartner: 3 +Zhang: 3 +Valerio: 3 +Tellez: 3 +Steffen: 3 +Shumate: 3 +Sauls: 3 +Ripley: 3 +Kemper: 3 +Jacks: 3 +Guffey: 3 +Evers: 3 +Craddock: 3 +Carvalho: 3 +Blaylock: 3 +Banuelos: 3 +Balderas: 3 +Wooden: 3 +Wheaton: 3 +Turnbull: 3 +Shuman: 3 +Pointer: 3 +Mosier: 3 +Mccue: 3 +Ligon: 3 +Kozlowski: 3 +Johansen: 3 +Ingle: 3 +Herr: 3 +Briones: 3 +Southern: 3 +Snipes: 3 +Rickman: 3 +Pipkin: 3 +Peace: 3 +Pantoja: 3 +Orosco: 3 +Moniz: 3 +Lawless: 3 +Kunkel: 3 +Hibbard: 3 +Galarza: 3 +Enos: 3 +Bussey: 3 +Settle: 3 +Schott: 3 +Salcido: 3 +Perreault: 3 +Mcdougal: 3 +Mccool: 3 +Haight: 3 +Garris: 3 +Ferry: 3 +Easton: 3 +Conyers: 3 +Atherton: 3 +Wimberly: 3 +Utley: 3 +Stephen: 3 +Spellman: 3 +Smithson: 3 +Slagle: 3 +Skipper: 3 +Ritchey: 3 +Rand: 3 +Petit: 3 +Osullivan: 3 +Oaks: 3 +Nutt: 3 +Mcvay: 3 +Mccreary: 3 +Mayhew: 3 +Knoll: 3 +Jewett: 3 +Harwood: 3 +Hailey: 3 +Cardoza: 3 +Ashe: 3 +Arriaga: 3 +Andres: 3 +Zeller: 3 +Wirth: 3 +Whitmire: 3 +Stauffer: 3 +Spring: 3 +Rountree: 3 +Redden: 3 +Mccaffrey: 3 +Martz: 3 +Loving: 3 +Larose: 3 +Langdon: 3 +Humes: 3 +Gaskin: 3 +Faber: 3 +Doll: 3 +Devito: 3 +Cass: 3 +Almond: 3 +Wingfield: 3 +Wingate: 3 +Villareal: 3 +Tyner: 3 +Smothers: 3 +Severson: 3 +Reno: 3 +Pennell: 3 +Maupin: 3 +Leighton: 3 +Janssen: 3 +Hassell: 3 +Hallman: 3 +Halcomb: 3 +Folse: 3 +Fitzsimmons: 3 +Fahey: 3 +Cranford: 3 +Bolen: 3 +Battles: 3 +Battaglia: 3 +Wooldridge: 3 +Weed: 3 +Trask: 3 +Rosser: 3 +Regalado: 3 +Mcewen: 3 +Keefe: 3 +Fuqua: 3 +Echevarria: 3 +Domingo: 3 +Dang: 3 +Caro: 3 +Boynton: 3 +Andrus: 3 +Wild: 3 +Viera: 3 +Vanmeter: 3 +Taber: 3 +Spradlin: 3 +Seibert: 3 +Provost: 3 +Prentice: 3 +Oliphant: 3 +Laporte: 3 +Hwang: 3 +Hatchett: 3 +Hass: 3 +Greiner: 3 +Freedman: 3 +Covert: 3 +Chilton: 3 +Byars: 3 +Wiese: 3 +Venegas: 3 +Swank: 3 +Shrader: 3 +Roderick: 3 +Roberge: 3 +Mullis: 3 +Mortensen: 3 +Mccune: 3 +Marlowe: 3 +Kirchner: 3 +Keck: 3 +Isaacson: 3 +Hostetler: 3 +Halverson: 3 +Gunther: 3 +Griswold: 3 +Gerard: 3 +Fenner: 3 +Durden: 3 +Blackwood: 3 +Bertram: 3 +Ahrens: 3 +Sawyers: 3 +Savoy: 3 +Nabors: 3 +Mcswain: 3 +Mackay: 3 +Loy: 3 +Lavender: 3 +Lash: 3 +Labbe: 3 +Jessup: 3 +Hubert: 3 +Fullerton: 3 +Donnell: 3 +Cruse: 3 +Crittenden: 3 +Correia: 3 +Centeno: 3 +Caudle: 3 +Canady: 3 +Callender: 3 +Alarcon: 3 +Ahern: 3 +Winfrey: 3 +Tribble: 3 +Tom: 3 +Styles: 3 +Salley: 3 +Roden: 3 +Musgrove: 3 +Minnick: 3 +Fortenberry: 3 +Carrion: 3 +Bunting: 3 +Bethel: 3 +Batiste: 3 +Woo: 3 +Whited: 3 +Underhill: 3 +Stillwell: 3 +Silvia: 3 +Rauch: 3 +Pippin: 3 +Perrin: 3 +Messenger: 3 +Mancini: 3 +Lister: 3 +Kinard: 3 +Hartmann: 3 +Fleck: 3 +Broadway: 3 +Wilt: 3 +Treadway: 3 +Thornhill: 3 +Speed: 3 +Spalding: 3 +Sam: 3 +Rafferty: 3 +Pitre: 3 +Patino: 3 +Ordonez: 3 +Linkous: 3 +Kelleher: 3 +Homan: 3 +Holiday: 3 +Galbraith: 3 +Feeney: 3 +Dorris: 3 +Curtin: 3 +Coward: 3 +Camarillo: 3 +Buss: 3 +Bunnell: 3 +Bolt: 3 +Beeler: 3 +Autry: 3 +Alcala: 3 +Witte: 3 +Wentz: 3 +Stidham: 3 +Shively: 3 +Nunley: 3 +Meacham: 3 +Martins: 3 +Lemke: 3 +Lefebvre: 3 +Kaye: 3 +Hynes: 3 +Horowitz: 3 +Hoppe: 3 +Holcombe: 3 +Estrella: 3 +Dunne: 3 +Derr: 3 +Cochrane: 3 +Brittain: 3 +Bedard: 3 +Beauregard: 3 +Torrence: 3 +Strunk: 3 +Soria: 3 +Simonson: 3 +Shumaker: 3 +Scoggins: 3 +Packer: 3 +Oconner: 3 +Moriarty: 3 +Leroy: 3 +Kuntz: 3 +Ives: 3 +Hutcheson: 3 +Horan: 3 +Hales: 3 +Garmon: 3 +Fitts: 3 +Dell: 3 +Bohn: 3 +Atchison: 3 +Worth: 3 +Wisniewski: 3 +Will: 3 +Vanwinkle: 3 +Sturm: 3 +Sallee: 3 +Prosser: 3 +Moen: 3 +Lundberg: 3 +Kunz: 3 +Kohl: 3 +Keane: 3 +Jorgenson: 3 +Jaynes: 3 +Funderburk: 3 +Freed: 3 +Frame: 3 +Durr: 3 +Creamer: 3 +Cosgrove: 3 +Candelaria: 3 +Berlin: 3 +Batson: 3 +Vanhoose: 3 +Thomsen: 3 +Teeter: 3 +Sommer: 3 +Smyth: 3 +Sena: 3 +Redmon: 3 +Orellana: 3 +Maness: 3 +Lennon: 3 +Heflin: 3 +Goulet: 3 +Frick: 3 +Forney: 3 +Dollar: 3 +Bunker: 3 +Asbury: 3 +Aguiar: 3 +Talbott: 3 +Southard: 3 +Pleasant: 3 +Mowery: 3 +Mears: 3 +Lemmon: 3 +Krieger: 3 +Hickson: 3 +Gracia: 3 +Elston: 3 +Duong: 3 +Delgadillo: 3 +Dayton: 3 +Dasilva: 3 +Conaway: 3 +Catron: 3 +Bruton: 3 +Bradbury: 3 +Bordelon: 3 +Bivins: 3 +Bittner: 3 +Bergstrom: 3 +Beals: 3 +Abell: 3 +Whelan: 3 +Travers: 3 +Tejada: 3 +Pulley: 3 +Pino: 3 +Norfleet: 3 +Nealy: 3 +Maes: 3 +Loper: 3 +Held: 3 +Gerald: 3 +Gatewood: 3 +Frierson: 3 +Freund: 3 +Finnegan: 3 +Cupp: 3 +Covey: 3 +Catalano: 3 +Boehm: 3 +Bader: 3 +Yoon: 3 +Walston: 3 +Tenney: 3 +Sipes: 3 +Roller: 3 +Rawlins: 3 +Medlock: 3 +Mccaskill: 3 +Mccallister: 3 +Marcotte: 3 +Maclean: 3 +Hughey: 3 +Henke: 3 +Harwell: 3 +Gladney: 3 +Gilson: 3 +Dew: 3 +Chism: 3 +Caskey: 3 +Brandenburg: 3 +Baylor: 3 +Villasenor: 3 +Veal: 3 +Van: 3 +Thatcher: 3 +Stegall: 3 +Shore: 3 +Petrie: 3 +Nowlin: 3 +Navarrete: 3 +Muhammad: 3 +Lombard: 3 +Loftin: 3 +Lemaster: 3 +Kroll: 3 +Kovach: 3 +Kimbrell: 3 +Kidwell: 3 +Hershberger: 3 +Fulcher: 3 +Eng: 3 +Cantwell: 3 +Bustos: 3 +Boland: 3 +Bobbitt: 3 +Binkley: 3 +Wester: 3 +Weis: 3 +Verdin: 3 +Tong: 3 +Tiller: 3 +Sisco: 3 +Sharkey: 3 +Seymore: 3 +Rosenbaum: 3 +Rohr: 3 +Quinonez: 3 +Pinkston: 3 +Nation: 3 +Malley: 3 +Logue: 3 +Lessard: 3 +Lerner: 3 +Lebron: 3 +Krauss: 3 +Klinger: 3 +Halstead: 3 +Haller: 3 +Getz: 3 +Burrow: 3 +Brant: 3 +Alger: 3 +Victor: 3 +Shores: 3 +Scully: 3 +Pounds: 3 +Pfeifer: 3 +Perron: 3 +Nelms: 3 +Munn: 3 +Mcmaster: 3 +Mckenney: 3 +Manns: 3 +Knudson: 3 +Hutchens: 3 +Huskey: 3 +Goebel: 3 +Flagg: 3 +Cushman: 3 +Click: 3 +Castellano: 3 +Carder: 3 +Bumgarner: 3 +Blaine: 3 +Bible: 3 +Wampler: 3 +Spinks: 3 +Robson: 3 +Neel: 3 +Mcreynolds: 3 +Mathias: 3 +Maas: 3 +Loera: 3 +Kasper: 3 +Jose: 3 +Jenson: 3 +Florez: 3 +Coons: 3 +Buckingham: 3 +Brogan: 3 +Berryman: 3 +Wilmoth: 3 +Wilhite: 3 +Thrash: 3 +Shephard: 3 +Seidel: 3 +Schulze: 3 +Roldan: 3 +Pettis: 3 +Obryan: 3 +Maki: 3 +Mackie: 3 +Hatley: 3 +Frazer: 3 +Fiore: 3 +Falls: 3 +Chesser: 3 +Bui: 3 +Bottoms: 3 +Bisson: 3 +Benefield: 3 +Allman: 3 +Wilke: 3 +Trudeau: 3 +Timm: 3 +Shifflett: 3 +Rau: 3 +Mundy: 3 +Milliken: 3 +Mayers: 3 +Leake: 3 +Kohn: 3 +Huntington: 3 +Horsley: 3 +Hermann: 3 +Guerin: 3 +Fryer: 3 +Frizzell: 3 +Foret: 3 +Flemming: 3 +Fife: 3 +Criswell: 3 +Carbajal: 3 +Bozeman: 3 +Boisvert: 3 +Archie: 3 +Antonio: 3 +Angulo: 3 +Wallen: 3 +Tapp: 3 +Silvers: 3 +Ramsay: 3 +Oshea: 3 +Orta: 3 +Moll: 3 +Mckeever: 3 +Mcgehee: 3 +Luciano: 3 +Linville: 3 +Kiefer: 3 +Ketchum: 3 +Howerton: 3 +Groce: 3 +Gaylord: 3 +Gass: 3 +Fusco: 3 +Corbitt: 3 +Blythe: 3 +Betz: 3 +Bartels: 3 +Amaral: 3 +Aiello: 3 +Yoo: 3 +Weddle: 3 +Troy: 3 +Sun: 3 +Sperry: 3 +Seiler: 3 +Runyan: 3 +Raley: 3 +Overby: 3 +Osteen: 3 +Olds: 3 +Mckeown: 3 +Mauro: 3 +Matney: 3 +Lauer: 3 +Lattimore: 3 +Hindman: 3 +Hartwell: 3 +Fredrickson: 3 +Fredericks: 3 +Espino: 3 +Clegg: 3 +Carswell: 3 +Cambell: 3 +Burkholder: 3 +August: 3 +Woodbury: 3 +Welker: 3 +Totten: 3 +Thornburg: 3 +Theriault: 3 +Stitt: 3 +Stamm: 3 +Stackhouse: 3 +Simone: 3 +Scholl: 3 +Saxon: 3 +Rife: 3 +Razo: 3 +Quinlan: 3 +Pinkerton: 3 +Olivo: 3 +Nesmith: 3 +Nall: 3 +Mattos: 3 +Leak: 3 +Lafferty: 3 +Justus: 3 +Giron: 3 +Geer: 3 +Fielder: 3 +Eagle: 3 +Drayton: 3 +Dortch: 3 +Conners: 3 +Conger: 3 +Chau: 3 +Boatwright: 3 +Billiot: 3 +Barden: 3 +Armenta: 3 +Antoine: 3 +Tibbetts: 3 +Steadman: 3 +Slattery: 3 +Sides: 3 +Rinaldi: 3 +Raynor: 3 +Rayford: 3 +Pinckney: 3 +Pettigrew: 3 +Nickel: 3 +Milne: 3 +Matteson: 3 +Halsey: 3 +Gonsalves: 3 +Fellows: 3 +Durand: 3 +Desimone: 3 +Cowley: 3 +Cowles: 3 +Brill: 3 +Barham: 3 +Barela: 3 +Barba: 3 +Ashmore: 3 +Withrow: 3 +Valenti: 3 +Tejeda: 3 +Spriggs: 3 +Sayre: 3 +Salerno: 3 +Place: 3 +Peltier: 3 +Peel: 3 +Merriman: 3 +Matheson: 3 +Lowman: 3 +Lindstrom: 3 +Hyland: 3 +Homer: 3 +Ha: 3 +Giroux: 3 +Fries: 3 +Frasier: 3 +Earls: 3 +Dugas: 3 +Damon: 3 +Dabney: 3 +Collado: 3 +Briseno: 3 +Baxley: 3 +Andre: 3 +Word: 3 +Whyte: 3 +Wenger: 3 +Vanover: 3 +Vanburen: 3 +Thiel: 3 +Schindler: 3 +Schiller: 3 +Rigby: 3 +Pomeroy: 3 +Passmore: 3 +Marble: 3 +Manzo: 3 +Mahaffey: 3 +Lindgren: 3 +Laflamme: 3 +Greathouse: 3 +Fite: 3 +Ferrari: 3 +Calabrese: 3 +Bayne: 3 +Yamamoto: 3 +Wick: 3 +Townes: 3 +Thames: 3 +Steel: 3 +Reinhart: 3 +Peeler: 3 +Naranjo: 3 +Montez: 3 +Mcdade: 3 +Mast: 3 +Markley: 3 +Marchand: 3 +Leeper: 3 +Kong: 3 +Kellum: 3 +Hudgens: 3 +Hennessey: 3 +Hadden: 3 +Guess: 3 +Gainey: 3 +Coppola: 3 +Borrego: 3 +Bolling: 3 +Beane: 3 +Ault: 3 +Slaton: 3 +Poland: 3 +Pape: 3 +Null: 3 +Mulkey: 3 +Lightner: 3 +Langer: 3 +Hillard: 3 +Glasgow: 3 +Fabian: 3 +Ethridge: 3 +Enright: 3 +Derosa: 3 +Baskin: 3 +Alfred: 3 +Weinberg: 3 +Turman: 3 +Tinker: 3 +Somerville: 3 +Pardo: 3 +Noll: 3 +Lashley: 3 +Ingraham: 3 +Hiller: 3 +Hendon: 3 +Glaze: 3 +Flora: 3 +Cothran: 3 +Cooksey: 3 +Conte: 3 +Carrico: 3 +Apple: 3 +Abner: 3 +Wooley: 3 +Swope: 3 +Summerlin: 3 +Sturgis: 3 +Sturdivant: 3 +Stott: 3 +Spurgeon: 3 +Spillman: 3 +Speight: 3 +Roussel: 3 +Popp: 3 +Nutter: 3 +Mckeon: 3 +Mazza: 3 +Magnuson: 3 +Lanning: 3 +Kozak: 3 +Jankowski: 3 +Heyward: 3 +Forster: 3 +Corwin: 3 +Callaghan: 3 +Bays: 3 +Wortham: 3 +Usher: 3 +Theriot: 3 +Sayers: 3 +Sabo: 3 +Rupert: 3 +Poling: 3 +Nathan: 3 +Loya: 3 +Lieberman: 3 +Levi: 3 +Laroche: 3 +Labelle: 3 +Howes: 3 +Harr: 3 +Garay: 3 +Fogarty: 3 +Everson: 3 +Durkin: 3 +Dominquez: 3 +Chaves: 3 +Chambliss: 3 +Alfonso: 3 +Witcher: 3 +Wilber: 3 +Vieira: 3 +Vandiver: 3 +Terrill: 3 +Stoker: 3 +Schreiner: 3 +Nestor: 3 +Moorman: 3 +Liddell: 3 +Lew: 3 +Lawhorn: 3 +Krug: 3 +Irons: 3 +Hylton: 3 +Hollenbeck: 3 +Herrin: 3 +Hembree: 3 +Hair: 3 +Goolsby: 3 +Goodin: 3 +Gilmer: 3 +Foltz: 3 +Dinkins: 3 +Daughtry: 3 +Caban: 3 +Brim: 3 +Briley: 3 +Bilodeau: 3 +Bear: 3 +Wyant: 3 +Vergara: 3 +Tallent: 3 +Swearingen: 3 +Stroup: 3 +Sherry: 3 +Scribner: 3 +Roger: 3 +Quillen: 3 +Pitman: 3 +Monaco: 3 +Mccants: 3 +Maxfield: 3 +Martinson: 3 +Landon: 3 +Holtz: 3 +Flournoy: 3 +Brookins: 3 +Brody: 3 +Baumgardner: 3 +Angelo: 3 +Straub: 3 +Sills: 3 +Roybal: 3 +Roundtree: 3 +Oswalt: 3 +Money: 3 +Mcgriff: 3 +Mcdougall: 3 +Mccleary: 3 +Maggard: 3 +Gragg: 3 +Gooding: 3 +Godinez: 3 +Doolittle: 3 +Donato: 3 +Cowell: 3 +Cassell: 3 +Bracken: 3 +Appel: 3 +Ahmad: 3 +Zambrano: 3 +Reuter: 3 +Perea: 3 +Olive: 3 +Nakamura: 3 +Monaghan: 3 +Mickens: 3 +Mcclinton: 3 +Mcclary: 3 +Marler: 3 +Kish: 3 +Judkins: 3 +Gilbreath: 3 +Freese: 3 +Flanigan: 3 +Felts: 3 +Erdmann: 3 +Dodds: 3 +Chew: 3 +Brownell: 3 +Brazil: 3 +Boatright: 3 +Barreto: 3 +Slayton: 3 +Sandberg: 3 +Saldivar: 3 +Pettway: 3 +Odum: 3 +Narvaez: 3 +Moultrie: 3 +Montemayor: 3 +Merrell: 3 +Lees: 3 +Keyser: 3 +Hoke: 3 +Hardaway: 3 +Hannan: 3 +Gilbertson: 3 +Fogg: 3 +Dumont: 3 +Deberry: 3 +Coggins: 3 +Carrera: 3 +Buxton: 3 +Bucher: 3 +Broadnax: 3 +Beeson: 3 +Araujo: 3 +Appleton: 3 +Amundson: 3 +Aguayo: 3 +Ackley: 3 +Yocum: 3 +Worsham: 3 +Shivers: 3 +Shelly: 3 +Sanches: 3 +Sacco: 3 +Robey: 3 +Rhoden: 3 +Pender: 3 +Ochs: 3 +Mccurry: 3 +Madera: 3 +Luong: 3 +Luis: 3 +Knotts: 3 +Jackman: 3 +Heinrich: 3 +Hargrave: 3 +Gault: 3 +Forest: 3 +Comeaux: 3 +Chitwood: 3 +Child: 3 +Caraway: 3 +Boettcher: 3 +Bernhardt: 3 +Barrientos: 3 +Zink: 3 +Wickham: 3 +Whiteman: 3 +Thorp: 3 +Stillman: 3 +Settles: 3 +Schoonover: 3 +Roque: 3 +Riddell: 3 +Rey: 3 +Pilcher: 3 +Phifer: 3 +Novotny: 3 +Maple: 3 +Macleod: 3 +Hardee: 3 +Haase: 3 +Grider: 3 +Fredrick: 3 +Earnest: 3 +Doucette: 3 +Clausen: 3 +Christmas: 3 +Bevins: 3 +Beamon: 3 +Badillo: 3 +Tolley: 2 +Tindall: 2 +Soule: 2 +Snook: 2 +Sebastian: 2 +Seale: 2 +Pitcher: 2 +Pinkney: 2 +Pellegrino: 2 +Nowell: 2 +Nemeth: 2 +Nail: 2 +Mondragon: 2 +Mclane: 2 +Lundgren: 2 +Ingalls: 2 +Hudspeth: 2 +Hixson: 2 +Gearhart: 2 +Furlong: 2 +Downes: 2 +Dionne: 2 +Dibble: 2 +Deyoung: 2 +Cornejo: 2 +Camara: 2 +Brookshire: 2 +Boyette: 2 +Wolcott: 2 +Tracey: 2 +Surratt: 2 +Sellars: 2 +Segal: 2 +Salyer: 2 +Reeve: 2 +Rausch: 2 +Philips: 2 +Labonte: 2 +Haro: 2 +Gower: 2 +Freeland: 2 +Fawcett: 2 +Eads: 2 +Driggers: 2 +Donley: 2 +Collett: 2 +Cage: 2 +Bromley: 2 +Boatman: 2 +Ballinger: 2 +Baldridge: 2 +Volz: 2 +Trombley: 2 +Stonge: 2 +Silas: 2 +Shanahan: 2 +Rivard: 2 +Rhyne: 2 +Pedroza: 2 +Matias: 2 +Mallard: 2 +Jamieson: 2 +Hedgepeth: 2 +Hartnett: 2 +Estevez: 2 +Eskridge: 2 +Denman: 2 +Chiu: 2 +Chinn: 2 +Catlett: 2 +Carmack: 2 +Buie: 2 +Book: 2 +Bechtel: 2 +Beardsley: 2 +Bard: 2 +Ballou: 2 +Windsor: 2 +Ulmer: 2 +Storm: 2 +Skeen: 2 +Robledo: 2 +Rincon: 2 +Reitz: 2 +Piazza: 2 +Pearl: 2 +Munger: 2 +Moten: 2 +Mcmichael: 2 +Loftus: 2 +Ledet: 2 +Kersey: 2 +Groff: 2 +Fowlkes: 2 +Folk: 2 +Crumpton: 2 +Collette: 2 +Clouse: 2 +Bettis: 2 +Villagomez: 2 +Timmerman: 2 +Strom: 2 +Saul: 2 +Santoro: 2 +Roddy: 2 +Phillip: 2 +Penrod: 2 +Musselman: 2 +Macpherson: 2 +Leboeuf: 2 +Harless: 2 +Haddad: 2 +Guido: 2 +Golding: 2 +Fulkerson: 2 +Fannin: 2 +Dulaney: 2 +Dowdell: 2 +Deane: 2 +Cottle: 2 +Ceja: 2 +Cate: 2 +Bosley: 2 +Benge: 2 +Albritton: 2 +Voigt: 2 +Trowbridge: 2 +Soileau: 2 +Seely: 2 +Rome: 2 +Rohde: 2 +Pearsall: 2 +Paulk: 2 +Orth: 2 +Nason: 2 +Mota: 2 +Mcmullin: 2 +Marquardt: 2 +Madigan: 2 +Hoag: 2 +Gillum: 2 +Gayle: 2 +Gabbard: 2 +Fenwick: 2 +Fender: 2 +Eck: 2 +Danforth: 2 +Cushing: 2 +Cress: 2 +Creed: 2 +Cazares: 2 +Casanova: 2 +Bey: 2 +Bettencourt: 2 +Barringer: 2 +Baber: 2 +Stansberry: 2 +Schramm: 2 +Rutter: 2 +Rivero: 2 +Race: 2 +Oquendo: 2 +Necaise: 2 +Mouton: 2 +Montenegro: 2 +Miley: 2 +Mcgough: 2 +Marra: 2 +Macmillan: 2 +Lock: 2 +Lamontagne: 2 +Jasso: 2 +Jaime: 2 +Horst: 2 +Hetrick: 2 +Heilman: 2 +Gaytan: 2 +Gall: 2 +Fried: 2 +Fortney: 2 +Eden: 2 +Dingle: 2 +Desjardins: 2 +Dabbs: 2 +Burbank: 2 +Brigham: 2 +Breland: 2 +Beaman: 2 +Banner: 2 +Arriola: 2 +Yarborough: 2 +Wallin: 2 +Treat: 2 +Toscano: 2 +Stowers: 2 +Reiss: 2 +Pichardo: 2 +Orton: 2 +Mitchel: 2 +Michels: 2 +Mcnamee: 2 +Mccrory: 2 +Leatherman: 2 +Kell: 2 +Keister: 2 +Jerome: 2 +Horning: 2 +Hargett: 2 +Guay: 2 +Friday: 2 +Ferro: 2 +Deboer: 2 +Dagostino: 2 +Clemente: 2 +Christ: 2 +Carper: 2 +Bowler: 2 +Blanks: 2 +Beaudry: 2 +Willie: 2 +Towle: 2 +Tafoya: 2 +Stricklin: 2 +Strader: 2 +Soper: 2 +Sonnier: 2 +Sigmon: 2 +Schenk: 2 +Saddler: 2 +Rodman: 2 +Pedigo: 2 +Mendes: 2 +Lunn: 2 +Lohr: 2 +Lahr: 2 +Kingsbury: 2 +Jarman: 2 +Hume: 2 +Holliman: 2 +Hofmann: 2 diff --git a/tpcdsgen/data/location_types.dst b/tpcdsgen/data/location_types.dst new file mode 100644 index 00000000..1b1ca538 --- /dev/null +++ b/tpcdsgen/data/location_types.dst @@ -0,0 +1,10 @@ +------ +-- location_type +-- values weights +-- ======= ======= +-- 1. location type 1. uniform +-- 2. distribution freq +------ +single family: 1, 50 +condo: 1, 25 +apartment: 1, 25 diff --git a/tpcdsgen/data/marital_statuses.dst b/tpcdsgen/data/marital_statuses.dst new file mode 100644 index 00000000..6a353038 --- /dev/null +++ b/tpcdsgen/data/marital_statuses.dst @@ -0,0 +1,11 @@ +------ +-- marital_status +-- values weights +-- ====== ========= +-- 1. marital_status 1. uniform +------ +M: 1 +S: 1 +D: 1 +W: 1 +U: 1 diff --git a/tpcdsgen/data/men_class.dst b/tpcdsgen/data/men_class.dst new file mode 100644 index 00000000..9975157e --- /dev/null +++ b/tpcdsgen/data/men_class.dst @@ -0,0 +1,12 @@ +------ +-- men_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +accessories, 2: 1 +shirts, 2: 1 +pants, 2: 1 +sports-apparel, 2: 1 diff --git a/tpcdsgen/data/music_class.dst b/tpcdsgen/data/music_class.dst new file mode 100644 index 00000000..e94aa184 --- /dev/null +++ b/tpcdsgen/data/music_class.dst @@ -0,0 +1,12 @@ +------ +-- music_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +rock, 2: 1 +country, 2: 1 +pop, 2: 1 +classical, 2: 1 diff --git a/tpcdsgen/data/nouns.dst b/tpcdsgen/data/nouns.dst new file mode 100644 index 00000000..1278c3da --- /dev/null +++ b/tpcdsgen/data/nouns.dst @@ -0,0 +1,998 @@ +-- nouns +-- from the common word list +-- data weights +-- ==== ======= +-- 1: noun 1: frequency +years:91 +children:47 +women:41 +men:39 +things:34 +eyes:32 +days:32 +members:30 +others:29 +police:28 +problems:27 +times:25 +services:25 +months:25 +words:24 +areas:24 +groups:20 +hands:19 +cases:19 +systems:18 +patients:18 +hours:18 +companies:18 +terms:17 +minutes:17 +countries:17 +changes:17 +parents:16 +conditions:16 +workers:15 +ways:15 +students:15 +schools:15 +friends:15 +weeks:14 +studies:14 +feet:14 +rights:13 +questions:13 +parties:13 +books:13 +authorities:13 +results:12 +relations:12 +rates:12 +parts:12 +levels:12 +details:12 +activities:12 +teachers:11 +reasons:11 +products:11 +points:11 +numbers:11 +issues:11 +ideas:11 +forces:11 +events:11 +effects:11 +arms:11 +standards:10 +sales:10 +rules:10 +resources:10 +prices:10 +lines:10 +interests:10 +goods:10 +figures:10 +costs:10 +circumstances:10 +windows:9 +types:9 +policies:9 +plans:9 +needs:9 +miles:9 +methods:9 +jobs:9 +girls:9 +forms:9 +factors:9 +animals:9 +trees:8 +subjects:8 +shares:8 +pupils:8 +players:8 +places:8 +officers:8 +matters:8 +letters:8 +individuals:8 +houses:8 +firms:8 +features:8 +families:8 +differences:8 +courses:8 +cells:8 +boys:8 +views:7 +values:7 +users:7 +units:7 +sources:7 +skills:7 +proposals:7 +plants:7 +names:7 +ministers:7 +materials:7 +lives:7 +leaders:7 +items:7 +institutions:7 +facilities:7 +examples:7 +difficulties:7 +decisions:7 +customers:7 +clothes:7 +cars:7 +bodies:7 +benefits:7 +banks:7 +aspects:7 +affairs:7 +walls:6 +techniques:6 +steps:6 +states:6 +sides:6 +requirements:6 +reports:6 +relationships:6 +records:6 +purposes:6 +programmes:6 +principles:6 +pp.:6 +powers:6 +pounds:6 +patterns:6 +papers:6 +opportunities:6 +operations:6 +officials:6 +measures:6 +managers:6 +legs:6 +homes:6 +grounds:6 +games:6 +funds:6 +fingers:6 +employees:6 +elements:6 +elections:6 +efforts:6 +courts:6 +classes:6 +buildings:6 +arrangements:6 +applications:6 +visitors:5 +troops:5 +thousands:5 +streets:5 +sites:5 +rooms:5 +readers:5 +projects:5 +profits:5 +processes:5 +procedures:5 +pieces:5 +pictures:5 +organisations:5 +orders:5 +notes:5 +models:5 +meetings:5 +lips:5 +laws:5 +heads:5 +governments:5 +functions:5 +flowers:5 +fields:5 +feelings:5 +facts:5 +developments:5 +demands:5 +colleagues:5 +clients:5 +charges:5 +centres:5 +birds:5 +attitudes:5 +arts:5 +actions:5 +accounts:5 +weapons:4 +unions:4 +towns:4 +thoughts:4 +theories:4 +thanks:4 +tests:4 +teeth:4 +tears:4 +teams:4 +talks:4 +structures:4 +stories:4 +statements:4 +stars:4 +stages:4 +sports:4 +societies:4 +situations:4 +signs:4 +shoulders:4 +shops:4 +sections:4 +seconds:4 +seats:4 +schemes:4 +regulations:4 +regions:4 +provisions:4 +properties:4 +proceedings:4 +practices:4 +positions:4 +persons:4 +periods:4 +payments:4 +pages:4 +owners:4 +offices:4 +objects:4 +objectives:4 +nations:4 +movements:4 +markets:4 +machines:4 +losses:4 +lights:4 +kinds:4 +industries:4 +implications:4 +hundreds:4 +horses:4 +goals:4 +farmers:4 +employers:4 +duties:4 +drugs:4 +doors:4 +dogs:4 +documents:4 +doctors:4 +directors:4 +departments:4 +contracts:4 +consequences:4 +communities:4 +colours:4 +claims:4 +cities:4 +characters:4 +characteristics:4 +artists:4 +arguments:4 +yards:3 +writers:3 +wages:3 +vehicles:3 +tools:3 +tasks:3 +symptoms:3 +supporters:3 +stones:3 +stations:3 +stairs:3 +sons:3 +soldiers:3 +shows:3 +shoes:3 +shareholders:3 +servants:3 +sentences:3 +scientists:3 +roads:3 +residents:3 +representatives:3 +prisoners:3 +premises:3 +politicians:3 +photographs:3 +personnel:3 +partners:3 +paintings:3 +organizations:3 +options:3 +occasions:3 +newspapers:3 +neighbours:3 +negotiations:3 +mothers:3 +moments:3 +metres:3 +leaves:3 +languages:3 +kids:3 +instructions:3 +images:3 +guests:3 +findings:3 +films:3 +faces:3 +experts:3 +experiments:3 +experiences:3 +expectations:3 +eggs:3 +ears:3 +earnings:3 +discussions:3 +degrees:3 +criteria:3 +councils:3 +copies:3 +contents:3 +computers:3 +components:3 +communications:3 +comments:3 +clubs:3 +citizens:3 +churches:3 +centuries:3 +categories:3 +cards:3 +candidates:3 +businesses:3 +brothers:3 +branches:3 +attempts:3 +assets:3 +articles:3 +ages:3 +agents:3 +agencies:3 +advantages:3 +adults:3 +wives:2 +winners:2 +wings:2 +waves:2 +waters:2 +votes:2 +voters:2 +voices:2 +villages:2 +victims:2 +versions:2 +variations:2 +variables:2 +universities:2 +trousers:2 +trials:2 +trends:2 +transactions:2 +topics:2 +tonnes:2 +titles:2 +tickets:2 +texts:2 +taxes:2 +tables:2 +suggestions:2 +styles:2 +strategies:2 +stores:2 +spirits:2 +speakers:2 +sounds:2 +sorts:2 +songs:2 +solutions:2 +solicitors:2 +ships:2 +sheets:2 +sets:2 +sessions:2 +securities:2 +sectors:2 +sciences:2 +scenes:2 +savings:2 +samples:2 +roots:2 +roles:2 +rocks:2 +rivers:2 +risks:2 +restrictions:2 +responsibilities:2 +responses:2 +researchers:2 +relatives:2 +refugees:2 +reforms:2 +references:2 +recommendations:2 +reactions:2 +quantities:2 +qualities:2 +qualifications:2 +publications:2 +professionals:2 +priorities:2 +pressures:2 +practitioners:2 +posts:2 +possibilities:2 +passengers:2 +participants:2 +pairs:2 +origins:2 +offences:2 +observations:2 +obligations:2 +nurses:2 +nights:2 +muscles:2 +mountains:2 +modules:2 +minds:2 +millions:2 +messages:2 +memories:2 +mechanisms:2 +meals:2 +marks:2 +manufacturers:2 +males:2 +magistrates:2 +loans:2 +lists:2 +links:2 +limits:2 +libraries:2 +lessons:2 +lawyers:2 +ladies:2 +knees:2 +keys:2 +judges:2 +islands:2 +investors:2 +instruments:2 +injuries:2 +initiatives:2 +inches:2 +improvements:2 +humans:2 +hotels:2 +hospitals:2 +hopes:2 +holidays:2 +holes:2 +hills:2 +guns:2 +guidelines:2 +glasses:2 +germans:2 +genes:2 +generations:2 +gardens:2 +forests:2 +foods:2 +files:2 +females:2 +fees:2 +fears:2 +fans:2 +expenses:2 +estates:2 +errors:2 +ends:2 +emotions:2 +drivers:2 +dreams:2 +drawings:2 +doubts:2 +divisions:2 +directions:2 +devices:2 +designs:2 +democrats:2 +decades:2 +deaths:2 +daughters:2 +dates:2 +cuts:2 +customs:2 +critics:2 +controls:2 +contributions:2 +consumers:2 +considerations:2 +conservatives:2 +connections:2 +conclusions:2 +concerns:2 +concepts:2 +concentrations:2 +complaints:2 +committees:2 +colleges:2 +chemicals:2 +chapters:2 +causes:2 +cattle:2 +calls:2 +boxes:2 +boundaries:2 +boots:2 +bones:2 +bonds:2 +boats:2 +boards:2 +blocks:2 +bits:2 +bills:2 +beliefs:2 +beds:2 +bars:2 +bands:2 +babies:2 +awards:2 +authors:2 +attacks:2 +assumptions:2 +associations:2 +approaches:2 +answers:2 +amounts:2 +americans:2 +aims:2 +agreements:2 +acts:2 +accidents:2 +youngsters:1 +writings:1 +wounds:1 +worlds:1 +workshops:1 +woods:1 +witnesses:1 +wishes:1 +wines:1 +winds:1 +wheels:1 +weekends:1 +weaknesses:1 +wars:1 +volunteers:1 +volumes:1 +visits:1 +videos:1 +vessels:1 +vegetables:1 +varieties:1 +uses:1 +twins:1 +trusts:1 +trustees:1 +troubles:1 +travellers:1 +trains:1 +traditions:1 +trades:1 +traders:1 +tracks:1 +toys:1 +tourists:1 +tories:1 +tons:1 +tiles:1 +ties:1 +threats:1 +themes:1 +territories:1 +tensions:1 +tenants:1 +temperatures:1 +telecommunications:1 +technologies:1 +targets:1 +tanks:1 +tales:1 +tactics:1 +symbols:1 +surveys:1 +surroundings:1 +surfaces:1 +supplies:1 +suppliers:1 +sums:1 +successes:1 +substances:1 +subsidies:1 +strings:1 +strengths:1 +strangers:1 +stocks:1 +stands:1 +spots:1 +speeches:1 +specimens:1 +specialists:1 +spaces:1 +sizes:1 +sisters:1 +signals:1 +shots:1 +shelves:1 +shapes:1 +shadows:1 +settlements:1 +settings:1 +sequences:1 +senses:1 +seeds:1 +secrets:1 +seasons:1 +scots:1 +scores:1 +scholars:1 +scales:1 +sanctions:1 +russians:1 +runs:1 +rumours:1 +rows:1 +routes:1 +rounds:1 +roses:1 +rivals:1 +rises:1 +rewards:1 +revenues:1 +restaurants:1 +respondents:1 +respects:1 +reserves:1 +reservations:1 +requests:1 +republics:1 +representations:1 +remarks:1 +remains:1 +reductions:1 +recordings:1 +rebels:1 +rats:1 +ranks:1 +railways:1 +races:1 +quarters:1 +pubs:1 +publishers:1 +provinces:1 +protests:1 +proteins:1 +prospects:1 +proportions:1 +programs:1 +producers:1 +prizes:1 +privileges:1 +prisons:1 +priests:1 +presents:1 +preparations:1 +preferences:1 +prayers:1 +potatoes:1 +ports:1 +populations:1 +pools:1 +polls:1 +policemen:1 +poles:1 +poets:1 +poems:1 +pockets:1 +plates:1 +planes:1 +pilots:1 +phrases:1 +phenomena:1 +phases:1 +performances:1 +perceptions:1 +peoples:1 +pensions:1 +pensioners:1 +penalties:1 +peasants:1 +paths:1 +passages:1 +particles:1 +parameters:1 +panels:1 +pains:1 +packages:1 +outcomes:1 +organs:1 +organisms:1 +organisers:1 +opponents:1 +opinions:1 +operators:1 +offers:1 +offenders:1 +odds:1 +occupations:1 +observers:1 +objections:1 +novels:1 +notions:1 +networks:1 +nerves:1 +musicians:1 +museums:1 +movies:1 +moves:1 +motives:1 +molecules:1 +modes:1 +mistakes:1 +missiles:1 +mines:1 +miners:1 +minerals:1 +mice:1 +metals:1 +merchants:1 +measurements:1 +meanings:1 +matches:1 +masters:1 +masses:1 +margins:1 +maps:1 +mammals:1 +makers:1 +magazines:1 +lovers:1 +looks:1 +locations:1 +limitations:1 +liabilities:1 +lengths:1 +lectures:1 +leads:1 +layers:1 +lands:1 +lakes:1 +lads:1 +laboratories:1 +kings:1 +kilometres:1 +journals:1 +journalists:1 +jews:1 +jeans:1 +investments:1 +investigations:1 +interviews:1 +intervals:1 +interpretations:1 +interactions:1 +intentions:1 +instances:1 +insects:1 +inhabitants:1 +ingredients:1 +influences:1 +indicators:1 +indians:1 +increases:1 +incomes:1 +incidents:1 +incentives:1 +imports:1 +illustrations:1 +husbands:1 +households:1 +honours:1 +holders:1 +historians:1 +heroes:1 +heels:1 +hearts:1 +habits:1 +guards:1 +grants:1 +governors:1 +gods:1 +gifts:1 +gentlemen:1 +gates:1 +gaps:1 +galleries:1 +gains:1 +futures:1 +fruits:1 +frames:1 +fragments:1 +foundations:1 +fortunes:1 +foreigners:1 +followers:1 +folk:1 +floors:1 +flights:1 +flats:1 +flames:1 +fires:1 +fathers:1 +farms:1 +falls:1 +failures:1 +factories:1 +eyebrows:1 +expressions:1 +exports:1 +explanations:1 +exhibitions:1 +exercises:1 +executives:1 +exchanges:1 +exceptions:1 +examinations:1 +evenings:1 +estimates:1 +equations:1 +environments:1 +entries:1 +enterprises:1 +enquiries:1 +engines:1 +engineers:1 +enemies:1 +emissions:1 +edges:1 +economies:1 +drinks:1 +dollars:1 +districts:1 +disputes:1 +dishes:1 +diseases:1 +disciplines:1 +dimensions:1 +developers:1 +detectives:1 +designers:1 +descriptions:1 +deputies:1 +depths:1 +deposits:1 +demonstrations:1 +delegates:1 +definitions:1 +defendants:1 +defences:1 +debts:1 +deals:1 +dealers:1 +databases:1 +dangers:1 +damages:1 +curtains:1 +cups:1 +cultures:1 +crowds:1 +crops:1 +criticisms:1 +crimes:1 +crews:1 +creditors:1 +creatures:1 +couples:1 +counties:1 +councillors:1 +corporations:1 +corners:1 +conventions:1 +contexts:1 +contacts:1 +consultants:1 +constraints:1 +conflicts:1 +conferences:1 +concessions:1 +competitors:1 +comparisons:1 +communists:1 +commitments:1 +commentators:1 +combinations:1 +columns:1 +colonies:1 +collections:1 +coins:1 +clouds:1 +clergy:1 +circles:1 +cigarettes:1 +christians:1 +choices:1 +chips:1 +chiefs:1 +cheeks:1 +charts:1 +channels:1 +champions:1 +championships:1 +chairs:1 +chains:1 +cats:1 +casualties:1 +carers:1 +careers:1 +camps:1 +campaigns:1 +cameras:1 +calculations:1 +buyers:1 +businessmen:1 +buses:1 +budgets:1 +breasts:1 +bottles:1 +borders:1 +bombs:1 +blues:1 +blacks:1 +bishops:1 +beings:1 +bedrooms:1 +beans:1 +beaches:1 +bases:1 +barriers:1 +balls:1 +bags:1 +bacteria:1 +backs:1 +auditors:1 +audiences:1 +assessments:1 +aspirations:1 +armies:1 +architects:1 +appointments:1 +applicants:1 +appearances:1 +appeals:1 +angles:1 +ambitions:1 +alternatives:1 +allowances:1 +allies:1 +allegations:1 +advisers:1 +advertisements:1 +advances:1 +addresses:1 +actors:1 +acres:1 +acids:1 +achievements:1 +accountants:1 +abilities:1 diff --git a/tpcdsgen/data/prepositions.dst b/tpcdsgen/data/prepositions.dst new file mode 100644 index 00000000..bfff210f --- /dev/null +++ b/tpcdsgen/data/prepositions.dst @@ -0,0 +1,221 @@ +-- prepositions +-- from the common word list +-- values weights +-- ====== ======== +-- 1. preposition 1. weight +in:169586 +to:84535 +for:76889 +with:60602 +on:59030 +by:49067 +at:43557 +from:39087 +into:15239 +about:11594 +as:10556 +like:8786 +between:8703 +through:6762 +after:5897 +against:5354 +under:5319 +over:5024 +out of:4393 +without:4351 +within:4249 +during:4243 +before:3662 +such as:3161 +towards:2732 +up:2445 +including:2265 +among:2259 +upon:2257 +up to:2236 +across:2023 +behind:1949 +off:1819 +rather than:1684 +around:1650 +because of:1635 +according to:1534 +despite:1436 +since:1368 +down:1305 +per:1275 +near:1245 +above:1244 +throughout:1132 +away from:1096 +outside:1066 +beyond:1036 +until:895 +in terms of:856 +due to:856 +worth:817 +along:787 +on to:781 +as to:738 +inside:661 +plus:619 +apart from:592 +in front of:589 +together with:569 +beside:563 +below:549 +onto:522 +subject to:497 +beneath:488 +along with:475 +past:472 +via:456 +in relation to:428 +amongst:423 +other than:401 +in addition to:331 +in favour of:327 +unlike:309 +aged:306 +concerning:305 +prior to:296 +but for:294 +in spite of:275 +in respect of:273 +alongside:266 +next to:265 +as for:260 +ahead of:246 +on behalf of:245 +on top of:223 +regarding:219 +depending on:208 +in response to:195 +in accordance with:195 +except for:190 +in the light of:170 +except:170 +instead of:158 +in charge of:152 +with regard to:151 +in connection with:151 +by means of:149 +on the part of:148 +out:145 +in view of:145 +as opposed to:143 +by way of:133 +with respect to:128 +contrary to:128 +toward:125 +in conjunction with:122 +in line with:117 +till:113 +following:113 +in touch with:110 +amid:109 +in support of:103 +in search of:100 +relative to:96 +in return for:93 +versus:92 +let alone:82 +irrespective of:82 +besides:81 +with a view to:80 +considering:79 +owing to:77 +near to:77 +in excess of:77 +in place of:76 +in need of:76 +in common with:76 +as of:69 +underneath:65 +outside of:65 +excluding:64 +up until:59 +as regards:59 +aboard:54 +pending:53 +on board:53 +notwithstanding:53 +adjacent to:51 +on account of:49 +amidst:48 +in keeping with:44 +opposite:43 +in comparison with:43 +pursuant to:42 +as against:41 +minus:40 +in contact with:40 +in association with:40 +thanks to:38 +unto:37 +with reference to:36 +for fear of:36 +nearer to:34 +in pursuit of:33 +in possession of:32 +in case of:32 +by reason of:31 +in between:29 +aside from:29 +in answer to:28 +in aid of:27 +in regard to:25 +in proportion to:25 +as from:25 +as between:25 +save:23 +into line with:23 +in defence of:21 +save for:20 +as well as:20 +vice:19 +in receipt of:19 +pro:18 +than:16 +of:15 +nearest to:15 +in reply to:15 +in consultation with:15 +nearer:14 +in lieu of:14 +in accord with:14 +atop:14 +in defiance of:13 +pertaining to:12 +off of:12 +given:12 +astride:12 +nearest:11 +in light of:11 +subsequent to:10 +out of touch with:10 +alias:10 +respecting:9 +a la:9 +barring:7 +vis-a-vis:6 +times:6 +in face of:6 +but:6 +out of line with:5 +due:5 +in cooperation with:4 +excepting:4 +thru:3 +top of:2 +prp:2 +in-between:2 +in favor of:2 +ere:2 +afore:2 +terms of:1 +saving:1 +in quest of:1 +apropos:1 +addition to:1 +according:1 diff --git a/tpcdsgen/data/purchase_band.dst b/tpcdsgen/data/purchase_band.dst new file mode 100644 index 00000000..f18b773d --- /dev/null +++ b/tpcdsgen/data/purchase_band.dst @@ -0,0 +1,27 @@ +------ +-- purchase_band +------ +-- values weights +-- ====== ======== +-- 1. purchase_band 1. distribution +------ +500: 7 +1000: 7 +1500: 7 +2000: 7 +2500: 7 +3000: 7 +3500: 7 +4000: 5 +4500: 5 +5000: 5 +5500: 5 +6000: 5 +6500: 5 +7000: 5 +7500: 1 +8000: 1 +8500: 1 +9000: 1 +9500: 1 +10000: 1 diff --git a/tpcdsgen/data/return_reasons.dst b/tpcdsgen/data/return_reasons.dst new file mode 100644 index 00000000..35a7e753 --- /dev/null +++ b/tpcdsgen/data/return_reasons.dst @@ -0,0 +1,82 @@ +------ +-- return_reasons +------ +-- values weights +-- ----------------------- +-- 1. reason 1-6. not sure... none are ever used +------ +Package was damaged: 1, 0, 0, 0, 0, 0 +Stopped working: 1, 0, 0, 0, 0, 0 +Did not get it on time: 1, 0, 0, 0, 0, 0 +Not the product that was ordred: 1, 0, 0, 0, 0, 0 +Parts missing: 1, 0, 0, 0, 0, 0 +Does not work with a product that I have: 1, 0, 0, 0, 0, 0 +Gift exchange: 1, 0, 0, 0, 0, 0 +Did not like the color: 1, 0, 0, 0, 0, 0 +Did not like the model: 1, 0, 0, 0, 0, 0 +Did not like the make: 1, 0, 0, 0, 0, 0 +Did not like the warranty: 1, 0, 0, 0, 0, 0 +No service location in my area: 1, 0, 0, 0, 0, 0 +Found a better price in a store: 1, 0, 0, 0, 0, 0 +Found a better extended warranty in a store: 1, 0, 0, 0, 0, 0 +Not working any more: 1, 0, 0, 0, 0, 0 +Did not fit: 1, 0, 0, 0, 0, 0 +Wrong size: 1, 0, 0, 0, 0, 0 +Lost my job: 1, 0, 0, 0, 0, 0 +unauthoized purchase: 1, 0, 0, 0, 0, 0 +duplicate purchase: 1, 0, 0, 0, 0, 0 +its is a boy: 1, 0, 0, 0, 0, 0 +it is a girl: 1, 0, 0, 0, 0, 0 +reason 23: 1, 0, 0, 0, 0, 0 +reason 24: 1, 0, 0, 0, 0, 0 +reason 25: 1, 0, 0, 0, 0, 0 +reason 26: 1, 0, 0, 0, 0, 0 +reason 27: 1, 0, 0, 0, 0, 0 +reason 28: 1, 0, 0, 0, 0, 0 +reason 29: 1, 0, 0, 0, 0, 0 +reason 31: 1, 0, 0, 0, 0, 0 +reason 31: 1, 0, 0, 0, 0, 0 +reason 32: 1, 0, 0, 0, 0, 0 +reason 33: 1, 0, 0, 0, 0, 0 +reason 34: 1, 0, 0, 0, 0, 0 +reason 35: 1, 0, 0, 0, 0, 0 +reason 36: 1, 1, 0, 0, 0, 0 +reason 37: 1, 1, 0, 0, 0, 0 +reason 38: 1, 1, 0, 0, 0, 0 +reason 39: 1, 1, 0, 0, 0, 0 +reason 40: 1, 1, 0, 0, 0, 0 +reason 41: 1, 1, 0, 0, 0, 0 +reason 42: 1, 1, 0, 0, 0, 0 +reason 43: 1, 1, 0, 0, 0, 0 +reason 44: 1, 1, 0, 0, 0, 0 +reason 45: 1, 1, 0, 0, 0, 0 +reason 46: 1, 1, 1, 0, 0, 0 +reason 47: 1, 1, 1, 0, 0, 0 +reason 48: 1, 1, 1, 0, 0, 0 +reason 49: 1, 1, 1, 0, 0, 0 +reason 50: 1, 1, 1, 0, 0, 0 +reason 51: 1, 1, 1, 0, 0, 0 +reason 52: 1, 1, 1, 0, 0, 0 +reason 53: 1, 1, 1, 0, 0, 0 +reason 54: 1, 1, 1, 0, 0, 0 +reason 55: 1, 1, 1, 0, 0, 0 +reason 56: 1, 1, 1, 1, 0, 0 +reason 57: 1, 1, 1, 1, 0, 0 +reason 58: 1, 1, 1, 1, 0, 0 +reason 59: 1, 1, 1, 1, 0, 0 +reason 60: 1, 1, 1, 1, 0, 0 +reason 61: 1, 1, 1, 1, 0, 0 +reason 62: 1, 1, 1, 1, 0, 0 +reason 63: 1, 1, 1, 1, 0, 0 +reason 64: 1, 1, 1, 1, 0, 0 +reason 65: 1, 1, 1, 1, 0, 0 +reason 66: 1, 1, 1, 1, 1, 0 +reason 67: 1, 1, 1, 1, 1, 0 +reason 68: 1, 1, 1, 1, 1, 0 +reason 69: 1, 1, 1, 1, 1, 0 +reason 70: 1, 1, 1, 1, 1, 0 +reason 71: 1, 1, 1, 1, 1, 1 +reason 72: 1, 1, 1, 1, 1, 1 +reason 73: 1, 1, 1, 1, 1, 1 +reason 74: 1, 1, 1, 1, 1, 1 +reason 75: 1, 1, 1, 1, 1, 1 diff --git a/tpcdsgen/data/salutations.dst b/tpcdsgen/data/salutations.dst new file mode 100644 index 00000000..0fda0f2d --- /dev/null +++ b/tpcdsgen/data/salutations.dst @@ -0,0 +1,14 @@ +------ +-- salutations +-- values weights +-- ======= ========== +-- 1. title 1. gender neutral +-- 2. male +-- 3. female +------ +Mr.: 10, 10, 0 +Mrs.: 10, 0, 10 +Ms.: 10, 0, 10 +Miss: 10, 0, 10 +Sir: 10, 10, 0 +Dr.: 10, 10, 10 diff --git a/tpcdsgen/data/sentences.dst b/tpcdsgen/data/sentences.dst new file mode 100644 index 00000000..47d2cdcc --- /dev/null +++ b/tpcdsgen/data/sentences.dst @@ -0,0 +1,109 @@ +-- sentences.dst sentence forms +-- A = article +-- D = adverb +-- J = adjective +-- N = noun +-- P = preposition +-- T = terminator +-- V = verb +-- X = auxiliary +-- values weights +-- ========= ========= +-- 1. sentence form 1. uniform +------ +N VT: 1 +J N VT: 1 +J\, J N VT: 1 +D J N VT: 1 +N X VT: 1 +J N X VT: 1 +J\, J N X VT: 1 +D J N X VT: 1 +N V DT: 1 +J N V DT: 1 +J\, J N V DT: 1 +D J N V DT: 1 +N X V DT: 1 +J N X V DT: 1 +J\, J N X V DT: 1 +D J N X V DT: 1 +N V P A NT: 1 +J N V P A NT: 1 +J\, J N V P A NT: 1 +D J N V P A NT: 1 +N X V P A NT: 1 +J N X V P A NT: 1 +J\, J N X V P A NT: 1 +D J N X V P A NT: 1 +N V D P A NT: 1 +J N V D P A NT: 1 +J\, J N V D P A NT: 1 +D J N V D P A NT: 1 +N X V D P A NT: 1 +J N X V D P A NT: 1 +J\, J N X V D P A NT: 1 +D J N X V D P A NT: 1 +N V NT: 1 +J N V NT: 1 +J\, J N V NT: 1 +D J N V NT: 1 +N X V NT: 1 +J N X V NT: 1 +J\, J N X V NT: 1 +D J N X V NT: 1 +N V D NT: 1 +J N V D NT: 1 +J\, J N V D NT: 1 +D J N V D NT: 1 +N X V D NT: 1 +J N X V D NT: 1 +J\, J N X V D NT: 1 +D J N X V D NT: 1 +N V J NT: 1 +J N V J NT: 1 +J\, J N V J NT: 1 +D J N V J NT: 1 +N X V J NT: 1 +J N X V J NT: 1 +J\, J N X V J NT: 1 +D J N X V J NT: 1 +N V D J NT: 1 +J N V D J NT: 1 +J\, J N V D J NT: 1 +D J N V D J NT: 1 +N X V D J NT: 1 +J N X V D J NT: 1 +J\, J N X V D J NT: 1 +D J N X V D J J NT: 1 +N V J\, J NT: 1 +J N V J\, J NT: 1 +J\, J N V J\, J NT: 1 +D J N V J\, J NT: 1 +N X V J\, J NT: 1 +J N X V J\, J NT: 1 +J\, J N X V J\, J NT: 1 +D J N X V J\, J NT: 1 +N V D J\, J NT: 1 +J N V D J\, J NT: 1 +J\, J N V D J\, J NT: 1 +D J N V D J\, J NT: 1 +N X V D J\, J NT: 1 +J N X V D J\, J NT: 1 +J\, J N X V D J\, J NT: 1 +D J N X V D J\, J NT: 1 +N V D J NT: 1 +J N V D J NT: 1 +J\, J N V D J NT: 1 +D J N V D J NT: 1 +N X V D J NT: 1 +J N X V D J NT: 1 +J\, J N X V D J NT: 1 +D J N X V D J NT: 1 +N V D D J NT: 1 +J N V D D J NT: 1 +J\, J N V D D J NT: 1 +D J N V D D J NT: 1 +N X V D D J NT: 1 +J N X V D D J NT: 1 +J\, J N X V D D J NT: 1 +D J N X V D D J NT: 1 diff --git a/tpcdsgen/data/ship_mode_carrier.dst b/tpcdsgen/data/ship_mode_carrier.dst new file mode 100644 index 00000000..15f8295f --- /dev/null +++ b/tpcdsgen/data/ship_mode_carrier.dst @@ -0,0 +1,26 @@ +------ +-- ship_mode_carrier +-- values weights +-- ----------------------- +-- 1. carrier 1. uniform +------ +UPS: 1 +FEDEX: 1 +AIRBORNE: 1 +USPS: 1 +DHL: 1 +TBS: 1 +ZHOU: 1 +ZOUROS: 1 +MSC: 1 +LATVIAN: 1 +ALLIANCE: 1 +ORIENTAL: 1 +BARIAN: 1 +BOXBUNDLES: 1 +GREAT EASTERN: 1 +DIAMOND: 1 +RUPEKSA: 1 +GERMA: 1 +HARMSTORF: 1 +PRIVATECARRIER: 1 diff --git a/tpcdsgen/data/ship_mode_code.dst b/tpcdsgen/data/ship_mode_code.dst new file mode 100644 index 00000000..d81a9eba --- /dev/null +++ b/tpcdsgen/data/ship_mode_code.dst @@ -0,0 +1,13 @@ +------ +-- ship_mode_code +-- values weights +-- ----------------------- +-- 1. code 1. uniform +------ +AIR: 1 +SURFACE: 1 +SEA: 1 +BIKE: 1 +HAND CARRY: 1 +MESSENGER: 1 +COURIER: 1 diff --git a/tpcdsgen/data/ship_mode_type.dst b/tpcdsgen/data/ship_mode_type.dst new file mode 100644 index 00000000..e349bcfd --- /dev/null +++ b/tpcdsgen/data/ship_mode_type.dst @@ -0,0 +1,12 @@ +------ +-- ship_mode_type +-- values weights +-- ----------------------- +-- 1. type 1. uniform +------ +REGULAR: 1 +EXPRESS: 1 +NEXT DAY: 1 +OVERNIGHT: 1 +TWO DAY: 1 +LIBRARY: 1 diff --git a/tpcdsgen/data/shoe_class.dst b/tpcdsgen/data/shoe_class.dst new file mode 100644 index 00000000..89d18429 --- /dev/null +++ b/tpcdsgen/data/shoe_class.dst @@ -0,0 +1,12 @@ +------ +-- shoe_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +womens, 2: 1 +mens, 2: 1 +kids, 2: 1 +athletic, 2: 1 diff --git a/tpcdsgen/data/sizes.dst b/tpcdsgen/data/sizes.dst new file mode 100644 index 00000000..cad37263 --- /dev/null +++ b/tpcdsgen/data/sizes.dst @@ -0,0 +1,16 @@ +------ +-- sizes +-- size of a particular item, if applicable (based on category) +-- values weights +-- ----------------------- +-- 1. size 1. uniform +-- 2. for non-sized categories +-- 3. size distributtion +------ +petite: 1, 0, 17 +small: 1, 0, 17 +medium: 1, 0, 35 +large: 1, 0, 22 +extra large: 1, 0, 17 +economy: 1, 0, 5 +N/A: 1, 1, 4 diff --git a/tpcdsgen/data/sport_class.dst b/tpcdsgen/data/sport_class.dst new file mode 100644 index 00000000..6ce047e1 --- /dev/null +++ b/tpcdsgen/data/sport_class.dst @@ -0,0 +1,24 @@ +------ +-- sport_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +athletic shoes, 10: 1 +baseball, 10: 1 +basketball, 10: 1 +camping, 10: 1 +fitness, 10: 1 +football, 10: 1 +hockey, 10: 1 +outdoor, 10: 1 +optics, 10: 1 +pools, 10: 1 +archery, 10: 1 +guns, 10: 1 +sailing, 10: 1 +tennis, 10: 1 +fishing, 10: 1 +golf, 10: 1 diff --git a/tpcdsgen/data/street_names.dst b/tpcdsgen/data/street_names.dst new file mode 100644 index 00000000..4afa52d9 --- /dev/null +++ b/tpcdsgen/data/street_names.dst @@ -0,0 +1,98 @@ +-- street_names +-- from 1990 census +-- values weights +-- ========= ========= +-- 1. street name 1. default +-- 2. returns an empty string 50% of the time + +: 0, 317000 +Church: 4031, 4031 +Central: 2450, 2450 +Center: 3402, 3402 +College: 2468, 2468 +Twelfth: 489, 489 +12th: 2957, 2957 +South: 3570, 3570 +Lakeview: 2487, 2487 +West: 3656, 3656 +Miller: 2488, 2488 +Cherry: 3669, 3669 +Broadway: 2511, 2511 +Jackson: 3725, 3725 +Sycamore: 2533, 2533 +Elevnth: 669, 669 +11th: 3109, 3109 +Hillcrest: 2547, 2547 +Railroad: 3853, 3853 +Fifteenth: 240, 240 +15th: 2317, 2317 +Sunset: 3929, 3929 +Madison: 2578, 2578 +Mill: 3975, 3975 +Ash: 2589, 2589 +Willow: 4017, 2613 +Woodland: 2615, 2615 +Lincoln: 4044, 4044 +Locust: 2618, 2618 +Ridge: 4048, 4048 +Poplar: 2645, 2645 +North: 4074, 4074 +Green: 2652, 2652 +Spring: 4165, 4165 +Dogwood: 2653, 2653 +Tenth: 879, 879 +10th: 3492, 3492 +Lee: 2669, 2669 +Walnut: 4799, 4799 +Williams: 2682, 2682 +Hill: 4877, 4877 +Birch: 2754, 2754 +Lake: 4901, 4901 +Davis: 2769, 2769 +9th: 3793, 3793 +Ninth: 1115, 1115 +Laurel: 2780, 2780 +Washington: 4974, 4974 +Spruce: 2821, 2821 +View: 5202, 5202 +14th: 2536, 2536 +Fourteenth: 315, 315 +Elm: 5233, 5233 +Adams: 2856, 2856 +8th: 4172, 4172 +Eigth: 1352, 1352 +Franklin: 2882, 2882 +Cedar: 5644, 5644 +13th: 2610, 2610 +Thirteenth: 367, 367 +Maple: 6103, 6103 +Chestnut: 2994, 2994 +Pine: 6170, 6170 +East: 3056, 3056 +7th: 4635, 4635 +Seventh: 1742, 1742 +Smith: 3076, 3076 +Oak: 6946, 6946 +Valley: 3082, 3082 +6th: 5097, 5097 +Sixth: 2186, 2186 +Meadow: 3193, 3193 +Main: 7664, 7664 +River: 3220, 3220 +5th: 5532, 5532 +Fifth: 2654, 2654 +Wilson: 3268, 3268 +Park: 8926, 8926 +Hickory: 3297, 3297 +4th: 6183, 6183 +Fourth: 3007, 3007 +Jefferson: 3306, 3306 +1st: 6047, 6047 +First: 3851, 3851 +Forest: 3309, 3309 +3rd: 6564, 6564 +Third: 3567, 3567 +Johnson: 3325, 3325 +2nd: 6907, 6907 +Second: 3959, 3959 +Highland: 3347, 3347 diff --git a/tpcdsgen/data/street_types.dst b/tpcdsgen/data/street_types.dst new file mode 100644 index 00000000..3abf23c7 --- /dev/null +++ b/tpcdsgen/data/street_types.dst @@ -0,0 +1,28 @@ +------ +-- street_type +------ +-- values weights +-- ======= ====== +-- 1. type 1. uniform +------ + +Street:1 +ST:1 +Avenue:1 +Ave:1 +Boulevard:1 +Blvd:1 +Road:1 +RD:1 +Parkway:1 +Pkwy:1 +Way:1 +Wy:1 +Drive:1 +Dr.:1 +Circle:1 +Cir.:1 +Lane:1 +Ln:1 +Court:1 +Ct.:1 diff --git a/tpcdsgen/data/syllables.dst b/tpcdsgen/data/syllables.dst new file mode 100755 index 00000000..fd445c89 --- /dev/null +++ b/tpcdsgen/data/syllables.dst @@ -0,0 +1,16 @@ +-- +-- syllables.dst +-- values weights +-- ====== ========= +-- 1. syllable 1. uniform +--------- +bar:1 +ought:1 +able:1 +pri:1 +ese:1 +anti:1 +cally:1 +ation:1 +eing:1 +n st:1 diff --git a/tpcdsgen/data/terminators.dst b/tpcdsgen/data/terminators.dst new file mode 100644 index 00000000..a4bf4c6e --- /dev/null +++ b/tpcdsgen/data/terminators.dst @@ -0,0 +1,9 @@ +-- terminator.dst +-- NOTE: that the additional terminator's that were listed in the C dsdgen were never read +-- or included in the weight calculation, so they are omitted here. +-- values weights +-- ====== ========= +-- 1. terminator 1. weights +-------- +.: 10 +;: 2 diff --git a/tpcdsgen/data/top_domains.dst b/tpcdsgen/data/top_domains.dst new file mode 100644 index 00000000..d5aa9116 --- /dev/null +++ b/tpcdsgen/data/top_domains.dst @@ -0,0 +1,9 @@ +------ +-- top_domains +-- values weights +-- ====== ========= +-- 1. domain suffix 1. uniform +------ +com: 1 +org: 1 +edu: 1 diff --git a/tpcdsgen/data/units.dst b/tpcdsgen/data/units.dst new file mode 100644 index 00000000..218fcdf9 --- /dev/null +++ b/tpcdsgen/data/units.dst @@ -0,0 +1,27 @@ +------ +-- units +-- values weights +-- ----------------------- +-- 1. unit name 1. uniform +------ +Unknown: 400 +Each: 400 +Dozen: 400 +Case: 400 +Pallet: 400 +Gross: 400 +Carton: 400 +Box: 400 +Bunch: 400 +Bundle: 400 +Oz: 400 +Lb: 400 +Ton: 400 +Ounce: 400 +Pound: 400 +Tsp: 400 +Tbl: 400 +Cup: 400 +Dram: 400 +Gram: 400 +N/A: 400 diff --git a/tpcdsgen/data/vehicle_count.dst b/tpcdsgen/data/vehicle_count.dst new file mode 100644 index 00000000..05a2928e --- /dev/null +++ b/tpcdsgen/data/vehicle_count.dst @@ -0,0 +1,13 @@ +------ +-- vehicle_count +------ +-- values weights +-- ====== ======= +-- 1. count 1. weight +----------------------- +0: 17 +1: 17 +2: 35 +3: 22 +4: 5 +-1: 4 diff --git a/tpcdsgen/data/verbs.dst b/tpcdsgen/data/verbs.dst new file mode 100644 index 00000000..2ba50681 --- /dev/null +++ b/tpcdsgen/data/verbs.dst @@ -0,0 +1,1484 @@ +-- verbs +-- from the common word list +-- values weights +-- ====== ========== +-- 1. verb 1. weight +----- +make:523 +see:487 +get:464 +take:436 +go:409 +know:359 +say:281 +find:273 +give:265 +think:238 +come:235 +help:209 +use:177 +look:177 +want:168 +tell:167 +keep:166 +like:161 +work:158 +provide:157 +become:129 +put:123 +pay:114 +feel:112 +need:107 +leave:103 +meet:102 +bring:101 +ask:100 +understand:98 +show:97 +mean:94 +play:93 +try:91 +believe:89 +hear:87 +ensure:87 +stop:82 +move:82 +allow:82 +seem:81 +continue:80 +produce:79 +talk:77 +change:77 +stay:76 +buy:74 +live:73 +let:73 +run:72 +start:71 +consider:71 +accept:69 +turn:68 +remember:66 +develop:66 +lead:64 +include:64 +hold:64 +avoid:64 +carry:63 +offer:62 +create:62 +speak:61 +expect:61 +appear:61 +follow:60 +explain:60 +prevent:59 +happen:59 +call:59 +win:57 +support:57 +achieve:57 +reduce:56 +write:55 +set:55 +return:55 +learn:55 +increase:54 +stand:53 +join:53 +read:51 +improve:51 +apply:51 +sell:50 +remain:50 +reach:50 +build:50 +prove:49 +receive:48 +deal:47 +begin:46 +spend:45 +raise:45 +form:45 +establish:45 +decide:44 +wait:43 +protect:43 +lose:43 +fall:43 +maintain:42 +act:42 +send:41 +discuss:41 +suggest:40 +save:40 +cover:40 +cause:40 +break:40 +sit:39 +identify:39 +eat:39 +obtain:38 +face:38 +choose:38 +add:38 +encourage:37 +enable:37 +agree:37 +require:36 +pass:36 +enjoy:36 +draw:36 +afford:36 +open:35 +end:35 +control:35 +answer:35 +walk:34 +visit:34 +seek:34 +enter:34 +cut:34 +serve:33 +occur:33 +kill:33 +imagine:33 +grow:33 +forget:33 +cope:33 +die:32 +bear:32 +worry:31 +wish:31 +watch:31 +determine:31 +care:31 +affect:31 +share:30 +pick:30 +catch:30 +mind:29 +love:29 +gain:29 +fight:29 +exist:29 +examine:29 +clear:29 +manage:28 +fit:28 +describe:28 +attend:28 +sleep:27 +introduce:27 +drive:27 +check:27 +result:26 +involve:26 +survive:25 +replace:25 +remove:25 +promote:25 +operate:25 +claim:25 +argue:25 +admit:25 +rise:24 +respond:24 +recognise:24 +realise:24 +escape:24 +discover:24 +wear:23 +study:23 +represent:23 +reflect:23 +perform:23 +matter:23 +listen:23 +indicate:23 +extend:23 +contain:23 +complete:23 +assume:23 +present:22 +marry:22 +last:22 +fill:22 +depend:22 +assess:22 +secure:21 +pull:21 +mention:21 +concentrate:21 +close:21 +benefit:21 +assist:21 +travel:20 +prepare:20 +persuade:20 +lie:20 +investigate:20 +hope:20 +handle:20 +express:20 +cost:20 +treat:19 +teach:19 +suffer:19 +sound:19 +miss:19 +fly:19 +attract:19 +supply:18 +settle:18 +reveal:18 +retain:18 +report:18 +place:18 +notice:18 +match:18 +explore:18 +collect:18 +adopt:18 +throw:17 +succeed:17 +rely:17 +refer:17 +recover:17 +point:17 +force:17 +defend:17 +contribute:17 +confirm:17 +beat:17 +arrive:17 +arise:17 +appreciate:17 +touch:16 +test:16 +solve:16 +sign:16 +satisfy:16 +resist:16 +justify:16 +influence:16 +ignore:16 +hide:16 +drop:16 +distinguish:16 +deny:16 +demonstrate:16 +deliver:16 +define:16 +contact:16 +blame:16 +attempt:16 +suit:15 +rest:15 +pursue:15 +proceed:15 +note:15 +finish:15 +drink:15 +destroy:15 +compete:15 +arrange:15 +acquire:15 +vote:14 +vary:14 +trust:14 +recognize:14 +press:14 +prefer:14 +overcome:14 +lay:14 +impose:14 +hit:14 +generate:14 +focus:14 +fail:14 +expand:14 +exercise:14 +advise:14 +address:14 +account:14 +undertake:13 +suppose:13 +restore:13 +refuse:13 +record:13 +recall:13 +participate:13 +mark:13 +feed:13 +emerge:13 +date:13 +alter:13 +withdraw:12 +tackle:12 +ring:12 +regard:12 +preserve:12 +plan:12 +order:12 +measure:12 +laugh:12 +judge:12 +implement:12 +earn:12 +comply:12 +compare:12 +communicate:12 +bother:12 +attack:12 +appeal:12 +accommodate:12 +wonder:11 +strike:11 +resolve:11 +release:11 +relate:11 +realize:11 +observe:11 +limit:11 +lift:11 +invest:11 +inform:11 +hurt:11 +hang:11 +experience:11 +enhance:11 +detect:11 +cry:11 +cross:11 +count:11 +comment:11 +challenge:11 +back:11 +welcome:10 +view:10 +transfer:10 +sustain:10 +strengthen:10 +stick:10 +state:10 +sort:10 +sing:10 +select:10 +ride:10 +review:10 +repeat:10 +remind:10 +relax:10 +question:10 +purchase:10 +publish:10 +predict:10 +own:10 +negotiate:10 +monitor:10 +launch:10 +interpret:10 +guarantee:10 +exclude:10 +convince:10 +climb:10 +celebrate:10 +behave:10 +analyse:10 +acknowledge:10 +thank:9 +tend:9 +submit:9 +step:9 +spread:9 +specify:9 +search:9 +score:9 +risk:9 +reply:9 +reject:9 +recommend:9 +qualify:9 +push:9 +permit:9 +organise:9 +light:9 +land:9 +issue:9 +imply:9 +illustrate:9 +fulfil:9 +finance:9 +exploit:9 +employ:9 +display:9 +demand:9 +convey:9 +construct:9 +conclude:9 +commit:9 +approach:9 +announce:9 +abandon:9 +train:8 +switch:8 +stimulate:8 +slip:8 +shoot:8 +shake:8 +separate:8 +rule:8 +resign:8 +react:8 +pretend:8 +please:8 +name:8 +market:8 +link:8 +lend:8 +jump:8 +intervene:8 +interfere:8 +insist:8 +guess:8 +grant:8 +free:8 +facilitate:8 +evaluate:8 +engage:8 +enforce:8 +eliminate:8 +ease:8 +disappear:8 +direct:8 +convert:8 +consult:8 +conduct:8 +complain:8 +combine:8 +clean:8 +borrow:8 +boost:8 +belong:8 +wash:7 +warn:7 +wake:7 +trace:7 +sue:7 +spare:7 +smile:7 +shift:7 +retire:7 +restrict:7 +register:7 +practise:7 +possess:7 +kiss:7 +incorporate:7 +head:7 +gather:7 +forgive:7 +fix:7 +fetch:7 +exceed:7 +dry:7 +differ:7 +design:7 +damage:7 +constitute:7 +compensate:7 +charge:7 +cease:7 +capture:7 +burn:7 +breathe:7 +approve:7 +appoint:7 +adjust:7 +adapt:7 +accompany:7 +yield:6 +waste:6 +undermine:6 +transform:6 +trade:6 +swim:6 +stress:6 +steal:6 +smell:6 +shut:6 +sense:6 +resume:6 +relieve:6 +reinforce:6 +propose:6 +paint:6 +lower:6 +locate:6 +knock:6 +invite:6 +intend:6 +hate:6 +guide:6 +function:6 +fear:6 +emphasise:6 +doubt:6 +dominate:6 +dismiss:6 +delay:6 +declare:6 +dare:6 +dance:6 +correct:6 +cook:6 +consist:6 +conform:6 +combat:6 +clarify:6 +calculate:6 +blow:6 +balance:6 +aid:6 +upset:5 +tolerate:5 +threaten:5 +stretch:5 +store:5 +spot:5 +speed:5 +slow:5 +sink:5 +shout:5 +rush:5 +roll:5 +reverse:5 +respect:5 +rescue:5 +repay:5 +repair:5 +render:5 +regain:5 +recruit:5 +reassure:5 +protest:5 +print:5 +pray:5 +phone:5 +organize:5 +oppose:5 +occupy:5 +obey:5 +modify:5 +minimise:5 +list:5 +kick:5 +integrate:5 +install:5 +induce:5 +impress:5 +highlight:5 +harm:5 +halt:5 +grasp:5 +flow:5 +fire:5 +favour:5 +extract:5 +estimate:5 +drag:5 +divide:5 +disturb:5 +disclose:5 +derive:5 +counter:5 +confront:5 +conceal:5 +coincide:5 +co-operate:5 +cancel:5 +block:5 +assure:5 +assert:5 +amount:5 +aim:5 +advance:5 +absorb:5 +wipe:4 +undergo:4 +translate:4 +tie:4 +tear:4 +taste:4 +swallow:4 +suspect:4 +surrender:4 +surprise:4 +suppress:4 +supplement:4 +struggle:4 +stare:4 +split:4 +spell:4 +smoke:4 +shed:4 +shape:4 +seize:4 +sail:4 +safeguard:4 +revive:4 +reproduce:4 +renew:4 +regulate:4 +regret:4 +reform:4 +reconcile:4 +quote:4 +quit:4 +provoke:4 +promise:4 +progress:4 +pose:4 +plant:4 +perceive:4 +penetrate:4 +opt:4 +object:4 +murder:4 +mount:4 +mix:4 +maximise:4 +manipulate:4 +lack:4 +knit:4 +inspect:4 +initiate:4 +house:4 +hire:4 +hesitate:4 +hand:4 +greet:4 +grab:4 +foster:4 +formulate:4 +figure:4 +feature:4 +expose:4 +exert:4 +exchange:4 +entertain:4 +endure:4 +emphasize:4 +embrace:4 +effect:4 +dress:4 +dream:4 +double:4 +distribute:4 +dispose:4 +discourage:4 +dig:4 +devote:4 +devise:4 +deter:4 +defeat:4 +decline:4 +cure:4 +curb:4 +cool:4 +contemplate:4 +connect:4 +concern:4 +command:4 +collapse:4 +co-ordinate:4 +cast:4 +calm:4 +breed:4 +bind:4 +ban:4 +ascertain:4 +arrest:4 +anticipate:4 +admire:4 +administer:4 +abolish:4 +witness:3 +withstand:3 +widen:3 +weigh:3 +weaken:3 +warm:3 +wander:3 +upgrade:3 +update:3 +unite:3 +transport:3 +track:3 +top:3 +terminate:3 +telephone:3 +tax:3 +swing:3 +sweep:3 +suspend:3 +supervise:3 +summon:3 +suffice:3 +stir:3 +stem:3 +steer:3 +squeeze:3 +spoil:3 +speculate:3 +slide:3 +signal:3 +ship:3 +service:3 +scream:3 +ruin:3 +round:3 +retrieve:3 +restrain:3 +request:3 +reconsider:3 +rebuild:3 +race:3 +punish:3 +project:3 +profit:3 +prevail:3 +pour:3 +postpone:3 +pack:3 +offset:3 +merge:3 +master:3 +manufacture:3 +lock:3 +isolate:3 +interview:3 +inspire:3 +inhibit:3 +indulge:3 +hurry:3 +hunt:3 +honour:3 +heal:3 +guard:3 +govern:3 +frighten:3 +flourish:3 +flee:3 +fish:3 +fancy:3 +fade:3 +experiment:3 +exhibit:3 +execute:3 +excuse:3 +evolve:3 +entail:3 +endorse:3 +encounter:3 +elect:3 +drift:3 +divert:3 +disrupt:3 +disguise:3 +discriminate:3 +diminish:3 +deserve:3 +decrease:3 +criticise:3 +crack:3 +correspond:3 +copy:3 +contract:3 +contend:3 +consolidate:3 +confuse:3 +confess:3 +condemn:3 +conceive:3 +concede:3 +compromise:3 +comprehend:3 +complement:3 +commence:3 +comfort:3 +cheer:3 +cater:3 +bury:3 +burst:3 +bite:3 +bet:3 +bend:3 +base:3 +attain:3 +attach:3 +associate:3 +assemble:3 +apologise:3 +allocate:3 +alleviate:3 +advertise:3 +accelerate:3 +zero:2 +withhold:2 +wind:2 +weep:2 +wave:2 +warrant:2 +verify:2 +venture:2 +urge:2 +uphold:2 +underline:2 +underestimate:2 +trouble:2 +trigger:2 +trap:2 +transmit:2 +tour:2 +total:2 +tighten:2 +thrive:2 +tempt:2 +target:2 +tap:2 +swear:2 +sum:2 +substitute:2 +subscribe:2 +strip:2 +spring:2 +soften:2 +snap:2 +smooth:2 +simplify:2 +silence:2 +signify:2 +shrink:2 +shop:2 +shine:2 +seal:2 +sample:2 +sacrifice:2 +rub:2 +rid:2 +reward:2 +revise:2 +revert:2 +retreat:2 +reserve:2 +resemble:2 +research:2 +rent:2 +remedy:2 +refrain:2 +rectify:2 +reconstruct:2 +reclaim:2 +rate:2 +range:2 +rally:2 +rain:2 +quantify:2 +prosecute:2 +prescribe:2 +preclude:2 +preach:2 +practice:2 +post:2 +portray:2 +pop:2 +plead:2 +pin:2 +persist:2 +pause:2 +park:2 +panic:2 +owe:2 +overthrow:2 +override:2 +overlook:2 +outline:2 +offend:2 +obscure:2 +oblige:2 +no:2 +notify:2 +neglect:2 +muster:2 +minimize:2 +melt:2 +march:2 +lunch:2 +long:2 +load:2 +lessen:2 +leap:2 +lean:2 +invoke:2 +invent:2 +invade:2 +interrupt:2 +interact:2 +insure:2 +instruct:2 +insert:2 +inherit:2 +inflict:2 +infer:2 +incur:2 +import:2 +imitate:2 +host:2 +heat:2 +glance:2 +gauge:2 +freeze:2 +foresee:2 +float:2 +file:2 +export:2 +explode:2 +evoke:2 +evade:2 +erect:2 +eradicate:2 +equip:2 +envisage:2 +enquire:2 +encompass:2 +emulate:2 +embark:2 +elicit:2 +educate:2 +edit:2 +dwell:2 +drown:2 +drain:2 +draft:2 +divorce:2 +distract:2 +distort:2 +dissolve:2 +dispense:2 +discharge:2 +discern:2 +disagree:2 +differentiate:2 +dictate:2 +detail:2 +descend:2 +deprive:2 +depart:2 +deceive:2 +debate:2 +cultivate:2 +criticize:2 +creep:2 +credit:2 +counteract:2 +cooperate:2 +contest:2 +consume:2 +conserve:2 +consent:2 +conquer:2 +congratulate:2 +conflict:2 +confine:2 +confer:2 +comprise:2 +commemorate:2 +cling:2 +classify:2 +chat:2 +chase:2 +campaign:2 +brush:2 +broaden:2 +bolster:2 +boil:2 +boast:2 +bid:2 +betray:2 +beg:2 +award:2 +await:2 +avert:2 +attribute:2 +assign:2 +articulate:2 +arouse:2 +amend:2 +alert:2 +adhere:2 +activate:2 +accuse:2 +accumulate:2 +accomplish:2 +access:2 +abide:2 +wriggle:1 +wreck:1 +wrap:1 +worship:1 +worsen:1 +wield:1 +whisper:1 +weave:1 +waive:1 +volunteer:1 +visualize:1 +visualise:1 +violate:1 +veto:1 +vanish:1 +validate:1 +utter:1 +utilise:1 +unveil:1 +unravel:1 +unlock:1 +unload:1 +undo:1 +underpin:1 +uncover:1 +twist:1 +tune:1 +tuck:1 +trip:1 +trim:1 +tremble:1 +tread:1 +transcend:1 +topple:1 +tip:1 +tidy:1 +tick:1 +thwart:1 +testify:1 +tender:1 +tease:1 +swell:1 +sweat:1 +swap:1 +survey:1 +summarize:1 +summarise:1 +suck:1 +succumb:1 +substantiate:1 +subsidise:1 +subdue:1 +stumble:1 +stuff:1 +stroke:1 +strive:1 +stray:1 +strain:1 +straighten:1 +stifle:1 +steady:1 +star:1 +starve:1 +stamp:1 +staff:1 +stabilize:1 +stabilise:1 +square:1 +sponsor:1 +spin:1 +spill:1 +specialise:1 +spawn:1 +soothe:1 +soar:1 +soak:1 +sniff:1 +sneak:1 +snatch:1 +smash:1 +skip:1 +simulate:1 +shrug:1 +shoulder:1 +shorten:1 +shore:1 +shock:1 +shield:1 +shelter:1 +shave:1 +sharpen:1 +seduce:1 +screw:1 +screen:1 +scratch:1 +scrap:1 +scrape:1 +scramble:1 +scare:1 +scan:1 +scale:1 +savour:1 +sanction:1 +salvage:1 +sack:1 +rouse:1 +rot:1 +rotate:1 +root:1 +rock:1 +rob:1 +roam:1 +rival:1 +rip:1 +rewrite:1 +revoke:1 +rethink:1 +retaliate:1 +restructure:1 +restart:1 +resort:1 +reside:1 +resent:1 +replicate:1 +reopen:1 +renounce:1 +remark:1 +relocate:1 +relish:1 +relinquish:1 +rejoin:1 +reinstate:1 +rehearse:1 +refute:1 +refine:1 +redress:1 +redeem:1 +recycle:1 +recur:1 +recreate:1 +recoup:1 +reckon:1 +recite:1 +recapture:1 +reassess:1 +reassert:1 +rear:1 +reap:1 +reappear:1 +re-open:1 +re-establish:1 +ratify:1 +rape:1 +rank:1 +quell:1 +quarrel:1 +punch:1 +pump:1 +publicise:1 +prosper:1 +prop:1 +pronounce:1 +prompt:1 +prolong:1 +prohibit:1 +procure:1 +proclaim:1 +probe:1 +privatise:1 +prise:1 +presume:1 +prejudice:1 +precipitate:1 +precede:1 +praise:1 +position:1 +poison:1 +plunge:1 +plug:1 +plough:1 +plot:1 +placate:1 +pitch:1 +pinpoint:1 +picture:1 +photograph:1 +perpetuate:1 +perfect:1 +peer:1 +overwhelm:1 +overturn:1 +overtake:1 +overlap:1 +outweigh:1 +oust:1 +omit:1 +obstruct:1 +nominate:1 +nod:1 +necessitate:1 +near:1 +navigate:1 +narrow:1 +multiply:1 +mould:1 +motivate:1 +modernise:1 +moderate:1 +model:1 +mobilize:1 +mitigate:1 +mislead:1 +mimic:1 +migrate:1 +mess:1 +merit:1 +mend:1 +mediate:1 +maximize:1 +mature:1 +mate:1 +materialise:1 +mask:1 +map:1 +manoeuvre:1 +manifest:1 +lure:1 +loose:1 +loosen:1 +lodge:1 +lobby:1 +linger:1 +lighten:1 +lick:1 +license:1 +liberate:1 +liaise:1 +levy:1 +level:1 +legislate:1 +lecture:1 +labour:1 +label:1 +jeopardise:1 +jail:1 +iron:1 +intrude:1 +intimidate:1 +intercept:1 +intensify:1 +instil:1 +inquire:1 +injure:1 +inject:1 +impede:1 +impart:1 +impair:1 +illuminate:1 +hook:1 +heed:1 +haunt:1 +haul:1 +hasten:1 +harness:1 +harden:1 +harbour:1 +hammer:1 +grip:1 +grind:1 +grieve:1 +glimpse:1 +generalize:1 +generalise:1 +gaze:1 +furnish:1 +fuck:1 +frustrate:1 +frame:1 +forgo:1 +forge:1 +forestall:1 +forecast:1 +fool:1 +fold:1 +flush:1 +flower:1 +flood:1 +flash:1 +filter:1 +fend:1 +fathom:1 +fast:1 +fashion:1 +faint:1 +expire:1 +expel:1 +excite:1 +exaggerate:1 +evacuate:1 +escort:1 +equate:1 +envy:1 +entitle:1 +entice:1 +ensue:1 +enrich:1 +enlist:1 +enlarge:1 +endeavour:1 +endanger:1 +enclose:1 +enact:1 +empty:1 +emigrate:1 +embody:1 +embarrass:1 +elucidate:1 +elaborate:1 +echo:1 +dust:1 +dump:1 +duck:1 +drill:1 +donate:1 +dodge:1 +document:1 +dive:1 +diversify:1 +dissuade:1 +disregard:1 +dispute:1 +displace:1 +disperse:1 +dispel:1 +dismantle:1 +dislodge:1 +dislike:1 +disentangle:1 +discredit:1 +discount:1 +discipline:1 +discard:1 +disarm:1 +disappoint:1 +dip:1 +dine:1 +digest:1 +diagnose:1 +detract:1 +deteriorate:1 +detain:1 +detach:1 +despise:1 +despair:1 +desire:1 +designate:1 +desert:1 +depress:1 +deposit:1 +deploy:1 +depict:1 +denounce:1 +denote:1 +demolish:1 +delight:1 +delete:1 +delegate:1 +defy:1 +defuse:1 +defraud:1 +deflect:1 +defer:1 +deepen:1 +deduce:1 +decorate:1 +decipher:1 +decay:1 +dash:1 +curtail:1 +curl:1 +crush:1 +crumble:1 +crawl:1 +crash:1 +correlate:1 +coordinate:1 +convict:1 +convene:1 +contrast:1 +contradict:1 +constrain:1 +conjure:1 +confide:1 +compose:1 +complicate:1 +compile:1 +compel:1 +commission:1 +commend:1 +colour:1 +collaborate:1 +clinch:1 +cite:1 +circumvent:1 +circulate:1 +choke:1 +chew:1 +cheat:1 +chart:1 +characterize:1 +characterise:1 +channel:1 +chair:1 +carve:1 +capitalise:1 +camp:1 +bypass:1 +bump:1 +budge:1 +browse:1 +broadcast:1 +brighten:1 +breach:1 +boycott:1 +bow:1 +bowl:1 +bounce:1 +boot:1 +bomb:1 +board:1 +blind:1 +blend:1 +beware:1 +bat:1 +battle:1 +bar:1 +bargain:1 +banish:1 +average:1 +avail:1 +authorise:1 +augment:1 +assimilate:1 +aspire:1 +appropriate:1 +applaud:1 +appease:1 +apologize:1 +annoy:1 +anger:1 +amuse:1 +allay:1 +alienate:1 +affirm:1 +advocate:1 +accrue:1 +accord:1 +abuse:1 diff --git a/tpcdsgen/data/web_page_use.dst b/tpcdsgen/data/web_page_use.dst new file mode 100644 index 00000000..e56c3373 --- /dev/null +++ b/tpcdsgen/data/web_page_use.dst @@ -0,0 +1,13 @@ +------ +-- web_page_use +-- values weights +-- ----------------------- +-- 1. type 1. uniform +------ +general: 1 +order: 1 +welcome: 1 +ad: 1 +feedback: 1 +protected: 1 +dynamic: 1 diff --git a/tpcdsgen/data/women_class.dst b/tpcdsgen/data/women_class.dst new file mode 100644 index 00000000..863b13ab --- /dev/null +++ b/tpcdsgen/data/women_class.dst @@ -0,0 +1,12 @@ +------ +-- women_class +-- second level used to populate the item hierarchy +-- values weights +-- ----------------------- +-- 1. class name 1. uniform +-- 2. brand count +------ +dresses, 2: 1 +fragrances, 2: 1 +maternity, 2: 1 +swimwear, 2: 1 diff --git a/tpcdsgen/scripts/README.md b/tpcdsgen/scripts/README.md new file mode 100644 index 00000000..5d9659e0 --- /dev/null +++ b/tpcdsgen/scripts/README.md @@ -0,0 +1,341 @@ +# TPC-DS Test Scripts + +This directory contains scripts for testing the Rust TPC-DS implementation against the Java reference implementation. + +## Overview + +The testing infrastructure validates that the Rust port generates **byte-for-byte identical** output to the Java +implementation (which itself maintains bug-for-bug compatibility with the original C dsdgen). + +## Prerequisites + +You need the Java TPC-DS implementation for conformance testing. Use the bootstrap script to set it up: + +```bash +./scripts/bootstrap-java.sh +``` + +This will clone and build the Java implementation automatically. See the bootstrap section below for details. + +## Directory Structure + +``` +tpcdsgen/ +├── tests/ +│ └── fixtures/ # Generated reference data (gitignored) +│ └── scale-1/ # Scale factor 1 reference data +│ ├── call_center.dat +│ ├── warehouse.dat +│ └── ... (all 25 tables) +└── scripts/ + ├── bootstrap-java.sh # Setup Java TPC-DS implementation + ├── generate-fixtures.sh # Generate Java reference data + ├── compare-table.sh # Compare one table + ├── test-all-tables.sh # Test all ported tables + ├── clean-fixtures.sh # Clean up fixtures + └── README.md # This file +``` + +## Quick Start + +```bash +# 1. Bootstrap Java implementation (first time only) +./scripts/bootstrap-java.sh + +# 2. Generate reference fixtures +./scripts/generate-fixtures.sh + +# 3. Test all ported tables +./scripts/test-all-tables.sh +``` + +## Scripts + +### 0. `bootstrap-java.sh` - Setup Java TPC-DS Implementation + +**⚠️ Run this first!** Sets up the Java TPC-DS implementation needed for conformance testing. + +**Usage:** +```bash +# First time setup (clone and build) +./scripts/bootstrap-java.sh + +# Force rebuild +./scripts/bootstrap-java.sh --rebuild + +# Verify existing installation +./scripts/bootstrap-java.sh --verify + +# Show help +./scripts/bootstrap-java.sh --help +``` + +**What it does:** +1. Checks if Java and Maven are installed +2. Clones the Java TPC-DS repository from GitHub (if needed) +3. Builds the Java implementation with Maven +4. Runs a smoke test to verify it works + +**Requirements:** +- Java 11+ (e.g., `brew install openjdk@11`) +- Maven (e.g., `brew install maven`) +- Git + +**Environment Variables:** +- `TPCDS_JAVA_REPO` - Override the Java repo URL (default: https://github.com/trinodb/tpcds.git) + +**Output:** +- Clones to `../tpcds/` (parallel to this repo) +- Creates `../tpcds/target/tpcds-*-jar-with-dependencies.jar` + +**Time:** ~2-3 minutes (first run) + +### 1. `generate-fixtures.sh` - Generate Reference Data + +Generates TPC-DS tables using the Java implementation. This creates the "golden reference" data that Rust output is compared against. + +**Usage:** +```bash +# Generate all 25 tables (recommended first run) +./scripts/generate-fixtures.sh + +# Generate specific tables +./scripts/generate-fixtures.sh call_center warehouse + +# Quiet mode (minimal output) +./scripts/generate-fixtures.sh --quiet + +# Show help +./scripts/generate-fixtures.sh --help +``` + +**What it does:** +1. Checks if Java implementation is built (builds if needed) +2. Creates `tests/fixtures/scale-1/` directory +3. Generates each table using Java TPC-DS generator +4. Reports progress and statistics + +**Output:** +- Generates `.dat` files in `tests/fixtures/scale-1/` +- Each file contains pipe-delimited rows with trailing pipe: `value1|value2|value3|` +- Files are gitignored (regenerate as needed) + +**Time:** ~2-5 minutes for all 25 tables at scale 1 + +--- + +### 2. `compare-table.sh` - Compare Single Table + +Compares Rust-generated output for a single table against the Java reference fixture. + +**Usage:** +```bash +# Compare a table +./scripts/compare-table.sh call_center + +# Quiet mode +./scripts/compare-table.sh customer_demographics --quiet + +# Show help +./scripts/compare-table.sh --help +``` + +**What it does:** +1. Checks that Java fixture exists +2. Generates table using Rust implementation +3. Performs byte-for-byte comparison with `diff` +4. Reports results + +**Exit codes:** +- `0` - Tables match exactly ✓ +- `1` - Tables differ or error occurred ✗ + +**Output example:** +``` +[INFO] ========================================= +[INFO] Table Comparison: call_center +[INFO] ========================================= +[INFO] Java fixture: tests/fixtures/scale-1/call_center.dat +[INFO] Generating call_center with Rust... +[INFO] Using binary: target/release/tpcdsgen --table call_center +[INFO] Comparing outputs... +[INFO] Java fixture: 6 rows, 4.0K +[INFO] Rust output: 6 rows, 4.0K +[SUCCESS] ✓ call_center: Outputs match exactly (6 rows) +[INFO] ========================================= +``` + +--- + +### 3. `test-all-tables.sh` - Test All Ported Tables + +Runs comparison tests for all tables that have been ported to Rust. This is the main test suite. + +**Usage:** +```bash +# Test all ported tables (verbose) +./scripts/test-all-tables.sh + +# Quiet mode (show only summary) +./scripts/test-all-tables.sh --quiet + +# Show help +./scripts/test-all-tables.sh --help +``` + +**What it does:** +1. Tests all 24 TPC-DS tables (dbgen_version excluded - has timestamps) +2. Builds the unified Rust generator (`tpcdsgen`) +3. Compares each table against Java fixture using `compare-table.sh` +4. Prints comprehensive summary + +**Exit codes:** +- `0` - All tables match ✓ +- `1` - One or more tables differ ✗ + +**Output example:** +``` +[INFO] ========================================= +[INFO] TPC-DS Table Test Suite +[INFO] ========================================= +[INFO] Testing 24 tables: +[INFO] - call_center +[INFO] - catalog_page +[INFO] - catalog_returns +[INFO] ... (all 24 tables) +[INFO] ========================================= +[INFO] Building Rust TPC-DS generator... +[SUCCESS] Generator built successfully +[INFO] ========================================= + +[INFO] Testing: call_center +... +[SUCCESS] ✓ call_center: Outputs match exactly (6 rows) +... + +[INFO] ========================================= +[INFO] Test Summary +[INFO] ========================================= +[INFO] Total tables tested: 24 +[SUCCESS] Passed: 24 + +[INFO] Total time: 45s +[INFO] ========================================= +``` + +--- + +### 4. `clean-fixtures.sh` - Clean Up Fixtures + +Removes all generated fixtures to free up disk space or force regeneration. + +**Usage:** +```bash +# Clean with confirmation prompt +./scripts/clean-fixtures.sh + +# Clean without confirmation +./scripts/clean-fixtures.sh --yes + +# Show help +./scripts/clean-fixtures.sh --help +``` + +**What it does:** +1. Counts fixture files and reports total size +2. Asks for confirmation (unless `--yes` provided) +3. Deletes entire `tests/fixtures/` directory + +--- + +## Typical Workflow + +### Initial Setup +```bash +# 1. Generate all reference fixtures (one-time, or when Java changes) +./scripts/generate-fixtures.sh + +# This creates tests/fixtures/scale-1/*.dat files +``` + +### During Development +```bash +# 2. After implementing a new table, compare it +./scripts/compare-table.sh new_table_name + +# 3. Or test all ported tables at once +./scripts/test-all-tables.sh +``` + +### Cleanup +```bash +# 4. Remove fixtures if needed (can regenerate anytime) +./scripts/clean-fixtures.sh --yes +``` + +--- + +## Requirements + +- **Java:** Maven-built TPC-DS JAR at `../tpcds/target/tpcds-*-jar-with-dependencies.jar` +- **Rust:** Cargo-built `tpcdsgen` binary at `target/debug/tpcdsgen` or `target/release/tpcdsgen` +- **Disk space:** ~500MB-1GB for scale 1 fixtures + +--- + +## Troubleshooting + +**Problem:** `Java JAR not found` +```bash +cd ../tpcds +mvn clean package +``` + +**Problem:** `Rust binary not found` +```bash +cargo build --release +``` + +**Problem:** `Fixture not found` +```bash +./scripts/generate-fixtures.sh X +``` + +**Problem:** Tables don't match +1. Check if both implementations use same seed (should be deterministic) +2. Verify Rust port logic against Java source +3. Use `diff` output to find first difference +4. Debug specific row/column that differs + +--- + +## Integration with CI/CD + +These scripts are designed to be CI-friendly: + +```yaml +# Example GitHub Actions workflow +- name: Generate fixtures + run: ./scripts/generate-fixtures.sh --quiet + +- name: Test all tables + run: ./scripts/test-all-tables.sh --quiet +``` + +Exit codes make it easy to fail CI on mismatches. + +--- + +## TODOs + +- [ ] Support multiple scale factors (scale-10, scale-100) +- [ ] MD5 hash validation (faster than full diff for large tables) + +--- + +## Notes + +- **Fixtures are gitignored** - They're generated artifacts, not source code +- **Deterministic output** - Same seed always produces same data +- **Byte-for-byte equality** - Not just row count, complete binary match +- **Bug compatibility** - Maintains same quirks as Java/C versions (e.g., leap year bug) diff --git a/tpcdsgen/scripts/benchmark.sh b/tpcdsgen/scripts/benchmark.sh new file mode 100755 index 00000000..dd3e8d55 --- /dev/null +++ b/tpcdsgen/scripts/benchmark.sh @@ -0,0 +1,142 @@ +#!/bin/bash +# +# TPC-DS Benchmark Script +# Measures generation time for all tables at scale factors 1, 10, and 100 +# +# Usage: ./scripts/benchmark.sh [--no-output] [--json] [--scales "1 10 100"] +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Default settings +NO_OUTPUT="" +JSON="" +SCALES="1 10 100" +OUTPUT_DIR="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --no-output) + NO_OUTPUT="--no-output" + shift + ;; + --json) + JSON="--json" + shift + ;; + --scales) + SCALES="$2" + shift 2 + ;; + --output-dir) + OUTPUT_DIR="$2" + shift 2 + ;; + -h|--help) + echo "TPC-DS Benchmark Script" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --no-output Don't write output files (measure generation speed only)" + echo " --json Output results as JSON" + echo " --scales \"1 10\" Space-separated list of scale factors (default: \"1 10 100\")" + echo " --output-dir DIR Directory for output files (default: temp directory)" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 # Run benchmark at scales 1, 10, 100" + echo " $0 --no-output # Measure generation speed without writing files" + echo " $0 --scales \"1 10\" # Only test scales 1 and 10" + echo " $0 --json > results.json # Save results as JSON" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +cd "$PROJECT_DIR" + +# Build release binary +echo "Building release binary..." +cargo build --release --bin benchmark 2>&1 | grep -v "Compiling\|Finished" || true +echo "" + +BENCHMARK_BIN="$PROJECT_DIR/target/release/benchmark" + +if [[ ! -x "$BENCHMARK_BIN" ]]; then + echo "Error: benchmark binary not found at $BENCHMARK_BIN" + exit 1 +fi + +# Create temp directory if no output dir specified and not using --no-output +if [[ -z "$OUTPUT_DIR" && -z "$NO_OUTPUT" ]]; then + OUTPUT_DIR=$(mktemp -d) + CLEANUP_DIR=true +else + CLEANUP_DIR=false +fi + +# Results file for comparison +RESULTS_FILE="$PROJECT_DIR/benchmark_results_$(date +%Y%m%d_%H%M%S).txt" + +echo "==========================================" +echo "TPC-DS Rust Generator Benchmark" +echo "==========================================" +echo "Date: $(date)" +echo "Host: $(hostname)" +echo "CPU: $(sysctl -n machdep.cpu.brand_string 2>/dev/null || cat /proc/cpuinfo 2>/dev/null | grep 'model name' | head -1 | cut -d: -f2 || echo 'Unknown')" +echo "Scales: $SCALES" +echo "" + +if [[ -n "$JSON" ]]; then + echo "[" + first=true +fi + +for scale in $SCALES; do + if [[ -n "$JSON" ]]; then + if [[ "$first" == "true" ]]; then + first=false + else + echo "," + fi + else + echo "" + echo "==========================================" + echo "Scale Factor: $scale" + echo "==========================================" + fi + + if [[ -z "$NO_OUTPUT" ]]; then + SCALE_OUTPUT_DIR="$OUTPUT_DIR/scale_$scale" + mkdir -p "$SCALE_OUTPUT_DIR" + "$BENCHMARK_BIN" --scale "$scale" --output-dir "$SCALE_OUTPUT_DIR" $JSON + else + "$BENCHMARK_BIN" --scale "$scale" $NO_OUTPUT $JSON + fi +done + +if [[ -n "$JSON" ]]; then + echo "" + echo "]" +fi + +# Cleanup temp directory +if [[ "$CLEANUP_DIR" == "true" && -d "$OUTPUT_DIR" ]]; then + rm -rf "$OUTPUT_DIR" +fi + +if [[ -z "$JSON" ]]; then + echo "" + echo "==========================================" + echo "Benchmark complete" + echo "==========================================" +fi diff --git a/tpcdsgen/scripts/bootstrap-java.sh b/tpcdsgen/scripts/bootstrap-java.sh new file mode 100755 index 00000000..88512b2b --- /dev/null +++ b/tpcdsgen/scripts/bootstrap-java.sh @@ -0,0 +1,351 @@ +#!/usr/bin/env bash +# +# Bootstrap the Java TPC-DS implementation for conformance testing +# +# This script: +# 1. Clones the Java TPC-DS repository (if needed) +# 2. Builds the Java implementation +# 3. Verifies the build succeeded +# +# Usage: +# ./scripts/bootstrap-java.sh # Clone and build +# ./scripts/bootstrap-java.sh --rebuild # Force rebuild even if exists +# ./scripts/bootstrap-java.sh --verify # Just verify, don't clone/build + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TPCDS_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)" +JAVA_DIR="$TPCDS_ROOT/tpcds" + +# Configuration +JAVA_REPO_URL="${TPCDS_JAVA_REPO:-https://github.com/trinodb/tpcds.git}" +FORCE_REBUILD=0 +VERIFY_ONLY=0 + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $*" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $*" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" >&2 +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +# Print usage +usage() { + cat << EOF +Bootstrap the Java TPC-DS implementation for conformance testing + +Usage: + $(basename "$0") [OPTIONS] + +Options: + --rebuild Force rebuild even if JAR exists + --verify Only verify installation, don't clone/build + --help Show this help message + +Environment Variables: + TPCDS_JAVA_REPO Git URL for Java TPC-DS repo + Default: https://github.com/trinodb/tpcds.git + +Examples: + $(basename "$0") # Clone and build if needed + $(basename "$0") --rebuild # Force clean rebuild + $(basename "$0") --verify # Just check if everything works + +EOF + exit 0 +} + +# Check if Java/Maven are installed +check_prerequisites() { + log_info "Checking prerequisites..." + + if ! command -v java &> /dev/null; then + log_error "Java is not installed" + log_error "Please install Java 11+ (e.g., 'brew install openjdk@11' on macOS)" + return 1 + fi + + if ! command -v mvn &> /dev/null; then + log_error "Maven is not installed" + log_error "Please install Maven (e.g., 'brew install maven' on macOS)" + return 1 + fi + + local java_version + java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | cut -d'.' -f1) + + log_success "Java version: $(java -version 2>&1 | head -1)" + log_success "Maven version: $(mvn -version 2>&1 | head -1)" + + if [[ $java_version -lt 11 ]]; then + log_warn "Java 11+ recommended (found version $java_version)" + fi + + return 0 +} + +# Find Java JAR file +find_java_jar() { + local jar_pattern="$JAVA_DIR/target/tpcds-*-jar-with-dependencies.jar" + local jar_file + + jar_file=$(ls $jar_pattern 2>/dev/null | head -1) + + if [[ -z "$jar_file" ]]; then + return 1 + fi + + echo "$jar_file" + return 0 +} + +# Clone the Java repository +clone_java_repo() { + log_info "Cloning Java TPC-DS repository..." + log_info "Source: $JAVA_REPO_URL" + log_info "Target: $JAVA_DIR" + + if [[ -d "$JAVA_DIR" ]]; then + log_warn "Directory already exists: $JAVA_DIR" + + # Check if it's a git repo + if [[ -d "$JAVA_DIR/.git" ]]; then + log_info "Existing git repository found, pulling latest changes..." + cd "$JAVA_DIR" + git pull origin master || log_warn "Failed to pull latest changes" + cd - >/dev/null + return 0 + else + log_error "Directory exists but is not a git repository" + log_error "Please remove $JAVA_DIR and try again" + return 1 + fi + fi + + # Clone the repository + if ! git clone "$JAVA_REPO_URL" "$JAVA_DIR"; then + log_error "Failed to clone Java repository" + return 1 + fi + + log_success "Successfully cloned Java TPC-DS repository" + return 0 +} + +# Build the Java implementation +build_java() { + log_info "Building Java TPC-DS implementation..." + + if [[ ! -d "$JAVA_DIR" ]]; then + log_error "Java directory does not exist: $JAVA_DIR" + return 1 + fi + + cd "$JAVA_DIR" + + # Clean build + log_info "Running: mvn clean package -DskipTests" + if ! mvn clean package -DskipTests; then + log_error "Maven build failed" + cd - >/dev/null + return 1 + fi + + cd - >/dev/null + + # Verify JAR was created + local jar_file + if jar_file=$(find_java_jar); then + local jar_size + jar_size=$(du -h "$jar_file" | cut -f1) + log_success "Build complete: $jar_file ($jar_size)" + return 0 + else + log_error "Build succeeded but JAR file not found" + return 1 + fi +} + +# Test the Java implementation +test_java() { + log_info "Testing Java TPC-DS implementation..." + + local jar_file + if ! jar_file=$(find_java_jar); then + log_error "JAR file not found" + return 1 + fi + + # Create temp directory for test + local temp_dir + temp_dir=$(mktemp -d) + + # Generate a small test table + log_info "Generating test table (reason) to verify installation..." + if java -jar "$jar_file" \ + --table reason \ + --scale 1 \ + --directory "$temp_dir" \ + --overwrite \ + > /dev/null 2>&1; then + + # Check output file + if [[ -f "$temp_dir/reason.dat" ]]; then + local row_count + row_count=$(wc -l < "$temp_dir/reason.dat" | tr -d ' ') + log_success "Test generation successful ($row_count rows)" + rm -rf "$temp_dir" + return 0 + else + log_error "Test generation failed - no output file" + rm -rf "$temp_dir" + return 1 + fi + else + log_error "Test generation failed" + rm -rf "$temp_dir" + return 1 + fi +} + +# Verify installation +verify_installation() { + log_info "Verifying Java TPC-DS installation..." + + # Check directory exists + if [[ ! -d "$JAVA_DIR" ]]; then + log_error "Java directory does not exist: $JAVA_DIR" + return 1 + fi + + # Check JAR exists + local jar_file + if ! jar_file=$(find_java_jar); then + log_error "JAR file not found in $JAVA_DIR/target/" + log_error "Run without --verify to build" + return 1 + fi + + log_success "Found JAR: $jar_file" + + # Test it works + if ! test_java; then + return 1 + fi + + log_success "Java TPC-DS installation verified" + return 0 +} + +# Main function +main() { + local start_time + local end_time + local duration + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --rebuild) + FORCE_REBUILD=1 + shift + ;; + --verify) + VERIFY_ONLY=1 + shift + ;; + --help) + usage + ;; + *) + log_error "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + done + + log_info "=========================================" + log_info "Java TPC-DS Bootstrap" + log_info "=========================================" + log_info "Java directory: $JAVA_DIR" + log_info "Repository: $JAVA_REPO_URL" + log_info "=========================================" + + start_time=$(date +%s) + + # Check prerequisites + if ! check_prerequisites; then + exit 1 + fi + + # If verify only, just check and exit + if [[ $VERIFY_ONLY -eq 1 ]]; then + if verify_installation; then + exit 0 + else + exit 1 + fi + fi + + # Clone repository if needed + if [[ ! -d "$JAVA_DIR" ]]; then + if ! clone_java_repo; then + exit 1 + fi + else + log_success "Java repository already exists" + fi + + # Build if needed or forced + local jar_file + if [[ $FORCE_REBUILD -eq 1 ]] || ! find_java_jar >/dev/null 2>&1; then + if ! build_java; then + exit 1 + fi + else + log_success "JAR already built: $(find_java_jar)" + fi + + # Test the installation + if ! test_java; then + exit 1 + fi + + end_time=$(date +%s) + duration=$((end_time - start_time)) + + echo "" + log_info "=========================================" + log_info "Bootstrap Complete" + log_info "=========================================" + log_success "Java TPC-DS is ready for conformance testing" + log_info "Time: ${duration}s" + log_info "" + log_info "Next steps:" + log_info " ./scripts/generate-fixtures.sh # Generate test fixtures" + log_info " ./scripts/test-all-tables.sh # Run conformance tests" + log_info "=========================================" +} + +main "$@" diff --git a/tpcdsgen/scripts/clean-fixtures.sh b/tpcdsgen/scripts/clean-fixtures.sh new file mode 100755 index 00000000..fa4df726 --- /dev/null +++ b/tpcdsgen/scripts/clean-fixtures.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# +# Clean up generated test fixtures +# +# Usage: +# ./scripts/clean-fixtures.sh [--yes] + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +FIXTURE_DIR="$PROJECT_ROOT/tests/fixtures" +SKIP_CONFIRM=0 + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $*" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $*" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" +} + +# Print usage +usage() { + cat << EOF +Clean up generated test fixtures + +Usage: + $(basename "$0") [--yes] + +Options: + --yes Skip confirmation prompt + +Examples: + $(basename "$0") # Clean with confirmation + $(basename "$0") --yes # Clean without confirmation + +EOF + exit 0 +} + +# Main function +main() { + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --yes) + SKIP_CONFIRM=1 + shift + ;; + --help) + usage + ;; + *) + log_warn "Unknown option: $1" + usage + ;; + esac + done + + log_info "=========================================" + log_info "Clean Test Fixtures" + log_info "=========================================" + + # Check if fixture directory exists + if [[ ! -d "$FIXTURE_DIR" ]]; then + log_info "No fixtures directory found: $FIXTURE_DIR" + log_info "Nothing to clean" + exit 0 + fi + + # Count files + local file_count + file_count=$(find "$FIXTURE_DIR" -type f -name "*.dat" | wc -l | tr -d ' ') + + if [[ $file_count -eq 0 ]]; then + log_info "No fixture files found" + log_info "Nothing to clean" + exit 0 + fi + + # Get total size + local total_size + total_size=$(du -sh "$FIXTURE_DIR" 2>/dev/null | cut -f1 || echo "unknown") + + log_info "Fixture directory: $FIXTURE_DIR" + log_info "Files to delete: $file_count .dat files" + log_info "Total size: $total_size" + log_info "" + + # Confirm deletion + if [[ $SKIP_CONFIRM -eq 0 ]]; then + log_warn "This will delete all generated fixtures!" + read -p "Are you sure? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "Cancelled" + exit 0 + fi + fi + + # Delete fixtures + log_info "Deleting fixtures..." + rm -rf "$FIXTURE_DIR" + + log_success "Fixtures cleaned successfully" + log_info "=========================================" +} + +main "$@" diff --git a/tpcdsgen/scripts/compare-table.sh b/tpcdsgen/scripts/compare-table.sh new file mode 100755 index 00000000..773f0a0b --- /dev/null +++ b/tpcdsgen/scripts/compare-table.sh @@ -0,0 +1,313 @@ +#!/usr/bin/env bash +# +# Compare Rust-generated table output with Java reference fixture +# +# Usage: +# ./scripts/compare-table.sh TABLE_NAME [--quiet] +# +# Exit codes: +# 0 - Tables match exactly +# 1 - Tables differ or error occurred + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Configuration (can be overridden by --scale) +SCALE_FACTOR=${TPCDS_SCALE:-1} +QUIET=0 + +# Logging functions +log_info() { + if [[ $QUIET -eq 0 ]]; then + echo -e "${BLUE}[INFO]${NC} $*" + fi +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $*" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +log_diff() { + echo -e "${YELLOW}[DIFF]${NC} $*" +} + +# Returns tables are generated by their parent sales generators +# Map returns table -> parent table (the one to generate) +get_generator_for_table() { + local table=$1 + case $table in + catalog_returns) echo "catalog_sales" ;; + store_returns) echo "store_sales" ;; + web_returns) echo "web_sales" ;; + *) echo "$table" ;; + esac +} + +# Print usage +usage() { + cat << EOF +Compare Rust-generated table output with Java reference fixture + +Usage: + $(basename "$0") TABLE_NAME [--quiet] + +Arguments: + TABLE_NAME Name of the table to compare (e.g., call_center) + +Options: + --quiet Quiet mode (minimal output) + +Examples: + $(basename "$0") call_center + $(basename "$0") customer_demographics --quiet + +Exit codes: + 0 - Tables match exactly + 1 - Tables differ or error occurred + +EOF + exit 0 +} + +# Find the unified tpcdsgen binary +find_rust_binary() { + local target_dir + + # Detect if we're in a workspace using cargo + if command -v cargo >/dev/null 2>&1; then + local workspace_root + workspace_root=$(cd "$PROJECT_ROOT" && cargo locate-project --workspace --message-format=plain 2>/dev/null | xargs dirname) + + if [[ -n "$workspace_root" && -d "$workspace_root" ]]; then + # In a workspace - use workspace target directory + target_dir="$workspace_root/target" + else + # Standalone project + target_dir="$PROJECT_ROOT/target" + fi + else + # No cargo available, fall back to PROJECT_ROOT + target_dir="$PROJECT_ROOT/target" + fi + + # Try release build + local binary="$target_dir/release/tpcdsgen" + if [[ -f "$binary" ]]; then + echo "$binary" + return 0 + fi + + # Try debug build + binary="$target_dir/debug/tpcdsgen" + if [[ -f "$binary" ]]; then + echo "$binary" + return 0 + fi + + return 1 +} + +# Generate table with Rust +generate_rust_table() { + local table=$1 + local output_file=$2 + local binary + local generator + + generator=$(get_generator_for_table "$table") + + if ! binary=$(find_rust_binary); then + log_error "Rust binary not found" + log_error "Build it with: cargo build --release" + return 1 + fi + + log_info "Generating $table with Rust..." + log_info "Using binary: $binary --table $generator --scale $SCALE_FACTOR" + if [[ "$generator" != "$table" ]]; then + log_info "Note: $table is generated alongside $generator" + fi + + # Create temp directory for generation + local temp_dir + temp_dir=$(mktemp -d) + + # Run Rust generator with --table, --scale, and --directory flags + if ! "$binary" --table "$generator" --scale "$SCALE_FACTOR" --directory "$temp_dir" >/dev/null 2>&1; then + log_error "Failed to generate $table with Rust" + rm -rf "$temp_dir" + return 1 + fi + + # Move generated file (table name, not generator name) + if [[ -f "$temp_dir/${table}.dat" ]]; then + mv "$temp_dir/${table}.dat" "$output_file" + rm -rf "$temp_dir" + return 0 + else + log_error "Expected Rust output file not found: $temp_dir/${table}.dat" + log_error "Files in temp dir: $(ls -la "$temp_dir")" + rm -rf "$temp_dir" + return 1 + fi +} + +# Compute MD5 hash (works on both macOS and Linux) +compute_md5() { + local file=$1 + if command -v md5sum >/dev/null 2>&1; then + md5sum "$file" | cut -d' ' -f1 + elif command -v md5 >/dev/null 2>&1; then + md5 -q "$file" + else + echo "NO_MD5" + fi +} + +# Compare two files +compare_files() { + local java_file=$1 + local rust_file=$2 + local table=$3 + + log_info "Comparing outputs..." + + # Get file sizes + local java_size + local rust_size + local java_rows + local rust_rows + + java_size=$(du -h "$java_file" | cut -f1) + rust_size=$(du -h "$rust_file" | cut -f1) + java_rows=$(wc -l < "$java_file" | tr -d ' ') + rust_rows=$(wc -l < "$rust_file" | tr -d ' ') + + log_info "Java fixture: $java_rows rows, $java_size" + log_info "Rust output: $rust_rows rows, $rust_size" + + # Quick check: row count must match + if [[ "$java_rows" != "$rust_rows" ]]; then + log_error "Row count mismatch!" + log_error " Java: $java_rows rows" + log_error " Rust: $rust_rows rows" + return 1 + fi + + # Compute MD5 hashes + log_info "Computing MD5 hashes..." + local java_md5 + local rust_md5 + java_md5=$(compute_md5 "$java_file") + rust_md5=$(compute_md5 "$rust_file") + + log_info "Java MD5: $java_md5" + log_info "Rust MD5: $rust_md5" + + # Compare MD5 hashes + if [[ "$java_md5" == "$rust_md5" ]]; then + log_success "✓ $table: MD5 match ($java_rows rows, $java_md5)" + return 0 + else + log_error "✗ $table: MD5 mismatch!" + log_error " Java: $java_md5" + log_error " Rust: $rust_md5" + log_diff "Showing first differences:" + diff -u "$java_file" "$rust_file" | head -30 || true + return 1 + fi +} + +# Main function +main() { + local table="" + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --scale) + SCALE_FACTOR="$2" + shift 2 + ;; + --quiet) + QUIET=1 + shift + ;; + --help) + usage + ;; + *) + if [[ -z "$table" ]]; then + table=$1 + else + log_error "Too many arguments" + usage + fi + shift + ;; + esac + done + + # Set fixture directory based on scale factor + FIXTURE_DIR="$PROJECT_ROOT/tests/fixtures/scale-$SCALE_FACTOR" + + # Validate table argument + if [[ -z "$table" ]]; then + log_error "Table name required" + usage + fi + + log_info "=========================================" + log_info "Table Comparison: $table" + log_info "=========================================" + + # Check if fixture exists + local fixture_file="$FIXTURE_DIR/${table}.dat" + if [[ ! -f "$fixture_file" ]]; then + log_error "Fixture not found: $fixture_file" + log_error "Generate fixtures first: ./scripts/generate-fixtures.sh $table" + exit 1 + fi + + log_info "Java fixture: $fixture_file" + + # Generate Rust output + local rust_output + rust_output=$(mktemp) + + if ! generate_rust_table "$table" "$rust_output"; then + rm -f "$rust_output" + exit 1 + fi + + log_info "Rust output: $rust_output (temporary)" + + # Compare files + local result=0 + if ! compare_files "$fixture_file" "$rust_output" "$table"; then + result=1 + fi + + # Cleanup + rm -f "$rust_output" + + log_info "=========================================" + + exit $result +} + +main "$@" diff --git a/tpcdsgen/scripts/generate-fixtures.sh b/tpcdsgen/scripts/generate-fixtures.sh new file mode 100755 index 00000000..4178f7b1 --- /dev/null +++ b/tpcdsgen/scripts/generate-fixtures.sh @@ -0,0 +1,280 @@ +#!/usr/bin/env bash +# +# Generate TPC-DS reference fixtures using the Java implementation +# +# Usage: +# ./scripts/generate-fixtures.sh # Generate all tables +# ./scripts/generate-fixtures.sh --quiet # Generate all tables (quiet mode) +# ./scripts/generate-fixtures.sh table1 ... # Generate specific tables +# ./scripts/generate-fixtures.sh --help # Show help + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +JAVA_DIR="$PROJECT_ROOT/../tpcds" + +# Configuration (can be overridden by --scale) +SCALE_FACTOR=${TPCDS_SCALE:-1} +QUIET=0 + +# All TPC-DS tables (25 tables) +ALL_TABLES=( + "call_center" + "catalog_page" + "catalog_returns" + "catalog_sales" + "customer" + "customer_address" + "customer_demographics" + "date_dim" + "dbgen_version" + "household_demographics" + "income_band" + "inventory" + "item" + "promotion" + "reason" + "ship_mode" + "store" + "store_returns" + "store_sales" + "time_dim" + "warehouse" + "web_page" + "web_returns" + "web_sales" + "web_site" +) + +# Logging functions +log_info() { + if [[ $QUIET -eq 0 ]]; then + echo -e "${BLUE}[INFO]${NC} $*" + fi +} + +log_success() { + if [[ $QUIET -eq 0 ]]; then + echo -e "${GREEN}[SUCCESS]${NC} $*" + fi +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" >&2 +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +# Print usage +usage() { + cat << EOF +Generate TPC-DS reference fixtures using the Java implementation + +Usage: + $(basename "$0") [OPTIONS] [TABLES...] + +Options: + --scale N Scale factor (default: 1) + --quiet Quiet mode (minimal output) + --help Show this help message + +Arguments: + TABLES Space-separated list of table names to generate + If omitted, generates all 25 tables + +Examples: + $(basename "$0") # Generate all tables at scale 1 + $(basename "$0") --scale 10 # Generate all tables at scale 10 + $(basename "$0") --quiet # Generate all tables (quiet) + $(basename "$0") call_center warehouse # Generate specific tables + +EOF + exit 0 +} + +# Find Java JAR file +find_java_jar() { + local jar_pattern="$JAVA_DIR/target/tpcds-*-jar-with-dependencies.jar" + local jar_file + + jar_file=$(ls $jar_pattern 2>/dev/null | head -1) + + if [[ -z "$jar_file" ]]; then + return 1 + fi + + echo "$jar_file" + return 0 +} + +# Build Java implementation if needed +ensure_java_build() { + log_info "Checking Java implementation..." + + if ! find_java_jar >/dev/null 2>&1; then + log_warn "Java JAR not found. Building Java implementation..." + + cd "$JAVA_DIR" + if ! mvn -q clean package -DskipTests; then + log_error "Failed to build Java implementation" + exit 1 + fi + cd - >/dev/null + + log_success "Java implementation built successfully" + else + log_info "Java JAR found: $(find_java_jar)" + fi +} + +# Generate a single table +generate_table() { + local table=$1 + local jar_file + jar_file=$(find_java_jar) + + log_info "Generating $table..." + + # Create a temporary directory for generation + local temp_dir + temp_dir=$(mktemp -d) + + # Generate table in temp directory + # + # Run Java generator, filter out DEBUG lines but capture errors + local output + if output=$(java -jar "$jar_file" \ + --table "$table" \ + --scale "$SCALE_FACTOR" \ + --overwrite \ + --directory "$temp_dir" \ + 2>&1); then + + # Move generated file to fixture directory + local output_file="$temp_dir/${table}.dat" + + if [[ -f "$output_file" ]]; then + mv "$output_file" "$FIXTURE_DIR/" + + # Get file info + local file_size + local row_count + file_size=$(du -h "$FIXTURE_DIR/${table}.dat" | cut -f1) + row_count=$(wc -l < "$FIXTURE_DIR/${table}.dat" | tr -d ' ') + + log_success "$table generated: $row_count rows, $file_size" + else + log_error "Expected output file not found: $output_file" + rm -rf "$temp_dir" + return 1 + fi + else + log_error "Failed to generate $table" + rm -rf "$temp_dir" + return 1 + fi + + # Clean up temp directory + rm -rf "$temp_dir" + return 0 +} + +# Main function +main() { + local tables_to_generate=() + local start_time + local end_time + local success_count=0 + local fail_count=0 + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --scale) + SCALE_FACTOR="$2" + shift 2 + ;; + --quiet) + QUIET=1 + shift + ;; + --help) + usage + ;; + *) + tables_to_generate+=("$1") + shift + ;; + esac + done + + # Set fixture directory based on scale factor + FIXTURE_DIR="$PROJECT_ROOT/tests/fixtures/scale-$SCALE_FACTOR" + + # If no tables specified, generate all + if [[ ${#tables_to_generate[@]} -eq 0 ]]; then + tables_to_generate=("${ALL_TABLES[@]}") + fi + + log_info "=========================================" + log_info "TPC-DS Fixture Generator" + log_info "=========================================" + log_info "Scale Factor: $SCALE_FACTOR" + log_info "Tables to generate: ${#tables_to_generate[@]}" + log_info "Fixture directory: $FIXTURE_DIR" + log_info "=========================================" + + # Ensure Java build exists + ensure_java_build + + # Create fixture directory + mkdir -p "$FIXTURE_DIR" + log_info "Created fixture directory: $FIXTURE_DIR" + + # Generate tables + start_time=$(date +%s) + + for table in "${tables_to_generate[@]}"; do + if generate_table "$table"; then + ((success_count++)) || true + else + ((fail_count++)) || true + fi + done + + end_time=$(date +%s) + local duration=$((end_time - start_time)) + + # Print summary + echo "" + log_info "=========================================" + log_info "Generation Complete" + log_info "=========================================" + log_success "Successfully generated: $success_count tables" + + if [[ $fail_count -gt 0 ]]; then + log_error "Failed to generate: $fail_count tables" + fi + + log_info "Total time: ${duration}s" + log_info "Fixtures saved to: $FIXTURE_DIR" + log_info "=========================================" + + # Exit with error if any tables failed + if [[ $fail_count -gt 0 ]]; then + exit 1 + fi +} + +main "$@" diff --git a/tpcdsgen/scripts/test-all-tables.sh b/tpcdsgen/scripts/test-all-tables.sh new file mode 100755 index 00000000..94951483 --- /dev/null +++ b/tpcdsgen/scripts/test-all-tables.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +# +# Test all ported Rust tables against Java reference fixtures +# +# Usage: +# ./scripts/test-all-tables.sh [--quiet] +# +# Exit codes: +# 0 - All tables match +# 1 - One or more tables differ + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Configuration (can be overridden by --scale) +SCALE_FACTOR=${TPCDS_SCALE:-1} +QUIET=0 + +# Logging functions +log_info() { + if [[ $QUIET -eq 0 ]]; then + echo -e "${BLUE}[INFO]${NC} $*" + fi +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $*" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" +} + +# Print usage +usage() { + cat << EOF +Test all ported Rust tables against Java reference fixtures + +Usage: + $(basename "$0") [--scale N] [--quiet] + +Options: + --scale N Scale factor (default: 1) + --quiet Quiet mode (show only summary) + +Examples: + $(basename "$0") # Test all tables at scale 1 + $(basename "$0") --scale 10 # Test all tables at scale 10 + $(basename "$0") --quiet # Test all tables (quiet) + +Exit codes: + 0 - All tables match exactly + 1 - One or more tables differ + +EOF + exit 0 +} + +# All TPC-DS tables to test (24 tables - excludes dbgen_version which has timestamps) +# Note: dbgen_version is excluded because it contains timestamps that will never match +ALL_TABLES=( + "call_center" + "catalog_page" + "catalog_returns" + "catalog_sales" + "customer" + "customer_address" + "customer_demographics" + "date_dim" + "household_demographics" + "income_band" + "inventory" + "item" + "promotion" + "reason" + "ship_mode" + "store" + "store_returns" + "store_sales" + "time_dim" + "warehouse" + "web_page" + "web_returns" + "web_sales" + "web_site" +) + +# Get list of tables to test +get_tables_to_test() { + echo "${ALL_TABLES[@]}" +} + +# Build the unified Rust table generator +build_generator() { + log_info "Building Rust TPC-DS generator..." + + if cargo build --release --quiet 2>&1; then + log_success "Generator built successfully" + return 0 + else + log_error "Failed to build Rust generator" + return 1 + fi +} + +# Test a single table +test_table() { + local table=$1 + local compare_script="$SCRIPT_DIR/compare-table.sh" + + if [[ $QUIET -eq 1 ]]; then + "$compare_script" "$table" --scale "$SCALE_FACTOR" --quiet + else + "$compare_script" "$table" --scale "$SCALE_FACTOR" + fi +} + +# Main function +main() { + local passed_tables=() + local failed_tables=() + local start_time + local end_time + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --scale) + SCALE_FACTOR="$2" + shift 2 + ;; + --quiet) + QUIET=1 + shift + ;; + --help) + usage + ;; + *) + log_error "Unknown option: $1" + usage + ;; + esac + done + + log_info "=========================================" + log_info "TPC-DS Table Test Suite" + log_info "Scale Factor: $SCALE_FACTOR" + log_info "=========================================" + + # Get tables to test + local tables_to_test + tables_to_test=$(get_tables_to_test) + local tables_array=($tables_to_test) + local total_count=${#tables_array[@]} + + log_info "Testing $total_count tables:" + for table in "${tables_array[@]}"; do + log_info " - $table" + done + log_info "=========================================" + + # Build generator + cd "$PROJECT_ROOT" + if ! build_generator; then + exit 1 + fi + log_info "=========================================" + + # Test each table + start_time=$(date +%s) + + for table in "${tables_array[@]}"; do + log_info "" + log_info "Testing: $table" + log_info "-----------------------------------------" + + if test_table "$table"; then + passed_tables+=("$table") + else + failed_tables+=("$table") + fi + + log_info "-----------------------------------------" + done + + end_time=$(date +%s) + local duration=$((end_time - start_time)) + + # Print summary + echo "" + log_info "=========================================" + log_info "Test Summary" + log_info "=========================================" + log_info "Total tables tested: $total_count" + log_success "Passed: ${#passed_tables[@]}" + + if [[ ${#failed_tables[@]} -gt 0 ]]; then + log_error "Failed: ${#failed_tables[@]}" + log_error "" + log_error "Failed tables:" + for table in "${failed_tables[@]}"; do + log_error " ✗ $table" + done + fi + + if [[ ${#passed_tables[@]} -gt 0 ]]; then + echo "" + log_success "Passed tables:" + for table in "${passed_tables[@]}"; do + log_success " ✓ $table" + done + fi + + log_info "" + log_info "Total time: ${duration}s" + log_info "=========================================" + + # Exit with error if any tables failed + if [[ ${#failed_tables[@]} -gt 0 ]]; then + exit 1 + fi + + exit 0 +} + +main "$@" diff --git a/tpcdsgen/src/bin/benchmark.rs b/tpcdsgen/src/bin/benchmark.rs new file mode 100644 index 00000000..17d342af --- /dev/null +++ b/tpcdsgen/src/bin/benchmark.rs @@ -0,0 +1,786 @@ +/* + * Benchmark binary for timing TPC-DS table generation at various scale factors. + * + * Usage: benchmark --scale --table [--output-dir ] + */ + +use clap::Parser; +use std::fs::File; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::time::Instant; +use tpcdsgen::config::{Session, Table}; +use tpcdsgen::output::Iso8859Writer; +use tpcdsgen::row::*; + +#[derive(Parser, Debug)] +#[command(name = "benchmark")] +#[command(about = "Benchmark TPC-DS table generation")] +struct Args { + /// Scale factor (1, 10, 100, etc.) + #[arg(short, long, default_value = "1")] + scale: f64, + + /// Table to generate (or "all" for all tables) + #[arg(short, long, default_value = "all")] + table: String, + + /// Output directory (default: current directory) + #[arg(short, long, default_value = ".")] + output_dir: PathBuf, + + /// Suppress output files (measure generation speed only) + #[arg(long, default_value = "false")] + no_output: bool, + + /// Output results as JSON + #[arg(long, default_value = "false")] + json: bool, +} + +fn create_session(scale: f64) -> Session { + Session::new( + scale, + ".".to_string(), + ".dat".to_string(), + None, + "".to_string(), + '|', + false, + false, + 1, + true, + ) +} + +struct BenchmarkResult { + table: String, + _scale: f64, + rows: i64, + duration_ms: u128, + rows_per_sec: f64, +} + +fn benchmark_table( + table_name: &str, + session: &Session, + output_dir: &Path, + no_output: bool, +) -> Result> { + let start = Instant::now(); + let mut row_count: i64 = 0; + + match table_name { + "call_center" => { + let mut gen = CallCenterRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::CallCenter); + if !no_output { + let file = File::create(output_dir.join("call_center.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "catalog_page" => { + let mut gen = CatalogPageRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::CatalogPage); + if !no_output { + let file = File::create(output_dir.join("catalog_page.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "customer" => { + let mut gen = CustomerRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Customer); + if !no_output { + let file = File::create(output_dir.join("customer.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "customer_address" => { + let mut gen = CustomerAddressRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::CustomerAddress); + if !no_output { + let file = File::create(output_dir.join("customer_address.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "customer_demographics" => { + let mut gen = CustomerDemographicsRowGenerator::new(); + let num_rows = session + .get_scaling() + .get_row_count(Table::CustomerDemographics); + if !no_output { + let file = File::create(output_dir.join("customer_demographics.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "date_dim" => { + let mut gen = DateDimRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::DateDim); + if !no_output { + let file = File::create(output_dir.join("date_dim.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "household_demographics" => { + let mut gen = HouseholdDemographicsRowGenerator::new(); + let num_rows = session + .get_scaling() + .get_row_count(Table::HouseholdDemographics); + if !no_output { + let file = File::create(output_dir.join("household_demographics.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "income_band" => { + let mut gen = IncomeBandRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::IncomeBand); + if !no_output { + let file = File::create(output_dir.join("income_band.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "item" => { + let mut gen = ItemRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Item); + if !no_output { + let file = File::create(output_dir.join("item.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "promotion" => { + let mut gen = PromotionRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Promotion); + if !no_output { + let file = File::create(output_dir.join("promotion.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "reason" => { + let mut gen = ReasonRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Reason); + if !no_output { + let file = File::create(output_dir.join("reason.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "ship_mode" => { + let mut gen = ShipModeRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::ShipMode); + if !no_output { + let file = File::create(output_dir.join("ship_mode.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "store" => { + let mut gen = StoreRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Store); + if !no_output { + let file = File::create(output_dir.join("store.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "time_dim" => { + let mut gen = TimeDimRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::TimeDim); + if !no_output { + let file = File::create(output_dir.join("time_dim.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "warehouse" => { + let mut gen = WarehouseRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Warehouse); + if !no_output { + let file = File::create(output_dir.join("warehouse.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "web_page" => { + let mut gen = WebPageRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::WebPage); + if !no_output { + let file = File::create(output_dir.join("web_page.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + "web_site" => { + let mut gen = WebSiteRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::WebSite); + if !no_output { + let file = File::create(output_dir.join("web_site.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + // Fact tables with child rows (sales + returns) + // Sales row is first in get_rows(), returns row is second (if present) + "store_sales" => { + let mut gen = StoreSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::StoreSales); + if !no_output { + let ss_file = File::create(output_dir.join("store_sales.dat"))?; + let sr_file = File::create(output_dir.join("store_returns.dat"))?; + let mut ss_writer = Iso8859Writer::new(BufWriter::new(ss_file)); + let mut sr_writer = Iso8859Writer::new(BufWriter::new(sr_file)); + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + // First row is store_sales + if !rows.is_empty() { + rows[0].write_to(&mut ss_writer, '|')?; + row_count += 1; + } + // Second row (if present) is store_returns + if rows.len() > 1 { + rows[1].write_to(&mut sr_writer, '|')?; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + ss_writer.flush()?; + sr_writer.flush()?; + } else { + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + if !rows.is_empty() { + row_count += 1; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + } + } + "catalog_sales" => { + let mut gen = CatalogSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::CatalogSales); + if !no_output { + let cs_file = File::create(output_dir.join("catalog_sales.dat"))?; + let cr_file = File::create(output_dir.join("catalog_returns.dat"))?; + let mut cs_writer = Iso8859Writer::new(BufWriter::new(cs_file)); + let mut cr_writer = Iso8859Writer::new(BufWriter::new(cr_file)); + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + if !rows.is_empty() { + rows[0].write_to(&mut cs_writer, '|')?; + row_count += 1; + } + if rows.len() > 1 { + rows[1].write_to(&mut cr_writer, '|')?; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + cs_writer.flush()?; + cr_writer.flush()?; + } else { + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + if !rows.is_empty() { + row_count += 1; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + } + } + "web_sales" => { + let mut gen = WebSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::WebSales); + if !no_output { + let ws_file = File::create(output_dir.join("web_sales.dat"))?; + let wr_file = File::create(output_dir.join("web_returns.dat"))?; + let mut ws_writer = Iso8859Writer::new(BufWriter::new(ws_file)); + let mut wr_writer = Iso8859Writer::new(BufWriter::new(wr_file)); + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + if !rows.is_empty() { + rows[0].write_to(&mut ws_writer, '|')?; + row_count += 1; + } + if rows.len() > 1 { + rows[1].write_to(&mut wr_writer, '|')?; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + ws_writer.flush()?; + wr_writer.flush()?; + } else { + let mut row_number = 1i64; + while row_number <= num_orders { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + if !rows.is_empty() { + row_count += 1; + } + if result.should_end_row() { + gen.consume_remaining_seeds_for_row(); + gen.consume_child_seeds(); + row_number += 1; + } + } + } + } + "inventory" => { + let mut gen = InventoryRowGenerator::new(); + let num_rows = session.get_scaling().get_row_count(Table::Inventory); + if !no_output { + let file = File::create(output_dir.join("inventory.dat"))?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + for row in result.get_rows() { + row.write_to(&mut writer, '|')?; + row_count += 1; + } + } + writer.flush()?; + } else { + for row_number in 1..=num_rows { + let result = + gen.generate_row_and_child_rows(row_number, session, None, None)?; + gen.consume_remaining_seeds_for_row(); + row_count += result.get_rows().len() as i64; + } + } + } + _ => { + return Err(format!("Unknown table: {}", table_name).into()); + } + } + + let duration = start.elapsed(); + let duration_ms = duration.as_millis(); + let rows_per_sec = if duration_ms > 0 { + row_count as f64 / (duration_ms as f64 / 1000.0) + } else { + 0.0 + }; + + Ok(BenchmarkResult { + table: table_name.to_string(), + _scale: session.get_scaling().get_scale(), + rows: row_count, + duration_ms, + rows_per_sec, + }) +} + +fn main() -> Result<(), Box> { + let args = Args::parse(); + let session = create_session(args.scale); + + let tables: Vec<&str> = if args.table == "all" { + vec![ + "call_center", + "catalog_page", + "catalog_sales", // includes catalog_returns + "customer", + "customer_address", + "customer_demographics", + "date_dim", + "household_demographics", + "income_band", + "inventory", + "item", + "promotion", + "reason", + "ship_mode", + "store", + "store_sales", // includes store_returns + "time_dim", + "warehouse", + "web_page", + "web_sales", // includes web_returns + "web_site", + ] + } else { + vec![args.table.as_str()] + }; + + let mut results: Vec = Vec::new(); + let total_start = Instant::now(); + + if !args.json { + println!("TPC-DS Benchmark - Scale Factor: {}", args.scale); + println!("============================================"); + println!( + "{:<25} {:>12} {:>12} {:>15}", + "Table", "Rows", "Time (ms)", "Rows/sec" + ); + println!("{:-<65}", ""); + } + + for table in &tables { + match benchmark_table(table, &session, &args.output_dir, args.no_output) { + Ok(result) => { + if !args.json { + println!( + "{:<25} {:>12} {:>12} {:>15.0}", + result.table, result.rows, result.duration_ms, result.rows_per_sec + ); + } + results.push(result); + } + Err(e) => { + eprintln!("Error generating {}: {}", table, e); + } + } + } + + let total_duration = total_start.elapsed(); + let total_rows: i64 = results.iter().map(|r| r.rows).sum(); + let total_ms = total_duration.as_millis(); + let total_rows_per_sec = if total_ms > 0 { + total_rows as f64 / (total_ms as f64 / 1000.0) + } else { + 0.0 + }; + + if args.json { + println!("{{"); + println!(" \"scale\": {},", args.scale); + println!(" \"total_rows\": {},", total_rows); + println!(" \"total_duration_ms\": {},", total_ms); + println!(" \"total_rows_per_sec\": {:.0},", total_rows_per_sec); + println!(" \"tables\": ["); + for (i, result) in results.iter().enumerate() { + let comma = if i < results.len() - 1 { "," } else { "" }; + println!( + " {{\"table\": \"{}\", \"rows\": {}, \"duration_ms\": {}, \"rows_per_sec\": {:.0}}}{}", + result.table, result.rows, result.duration_ms, result.rows_per_sec, comma + ); + } + println!(" ]"); + println!("}}"); + } else { + println!("{:-<65}", ""); + println!( + "{:<25} {:>12} {:>12} {:>15.0}", + "TOTAL", total_rows, total_ms, total_rows_per_sec + ); + println!("\nTotal time: {:.2}s", total_ms as f64 / 1000.0); + } + + Ok(()) +} diff --git a/tpcdsgen/src/business_key_generator.rs b/tpcdsgen/src/business_key_generator.rs new file mode 100644 index 00000000..6f3bb5b1 --- /dev/null +++ b/tpcdsgen/src/business_key_generator.rs @@ -0,0 +1,37 @@ +const BUSINESS_KEY_CHARS: &str = "ABCDEFGHIJKLMNOP"; + +pub fn make_business_key(primary: i64) -> String { + let key_part1 = long_to_8_char_string((primary >> 32) as u32); + let key_part2 = long_to_8_char_string(primary as u32); + format!("{}{}", key_part1, key_part2) +} + +fn long_to_8_char_string(mut value: u32) -> String { + let mut result = String::with_capacity(8); + for _ in 0..8 { + let char_index = (value & 0xF) as usize; + result.push(BUSINESS_KEY_CHARS.chars().nth(char_index).unwrap()); + value >>= 4; + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_business_key_generation() { + assert_eq!(make_business_key(1), "AAAAAAAABAAAAAAA"); + assert_eq!(make_business_key(2), "AAAAAAAACAAAAAAA"); + assert_eq!(make_business_key(3), "AAAAAAAADAAAAAAA"); + } + + #[test] + fn test_long_to_8_char_string() { + assert_eq!(long_to_8_char_string(0), "AAAAAAAA"); + assert_eq!(long_to_8_char_string(1), "BAAAAAAA"); + assert_eq!(long_to_8_char_string(15), "PAAAAAAA"); + assert_eq!(long_to_8_char_string(16), "ABAAAAAA"); + } +} diff --git a/tpcdsgen/src/column/call_center.rs b/tpcdsgen/src/column/call_center.rs new file mode 100644 index 00000000..1d21f777 --- /dev/null +++ b/tpcdsgen/src/column/call_center.rs @@ -0,0 +1,377 @@ +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +/// Call Center table columns (CallCenterColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CallCenterColumn { + CcCallCenterSk, + CcCallCenterId, + CcRecStartDate, + CcRecEndDate, + CcClosedDateSk, + CcOpenDateSk, + CcName, + CcClass, + CcEmployees, + CcSqFt, + CcHours, + CcManager, + CcMktId, + CcMktClass, + CcMktDesc, + CcMarketManager, + CcDivision, + CcDivisionName, + CcCompany, + CcCompanyName, + CcStreetNumber, + CcStreetName, + CcStreetType, + CcSuiteNumber, + CcCity, + CcCounty, + CcState, + CcZip, + CcCountry, + CcGmtOffset, + CcTaxPercentage, +} + +impl CallCenterColumn { + /// Get all columns in order + pub fn values() -> &'static [CallCenterColumn] { + use CallCenterColumn::*; + static VALUES: &[CallCenterColumn] = &[ + CcCallCenterSk, + CcCallCenterId, + CcRecStartDate, + CcRecEndDate, + CcClosedDateSk, + CcOpenDateSk, + CcName, + CcClass, + CcEmployees, + CcSqFt, + CcHours, + CcManager, + CcMktId, + CcMktClass, + CcMktDesc, + CcMarketManager, + CcDivision, + CcDivisionName, + CcCompany, + CcCompanyName, + CcStreetNumber, + CcStreetName, + CcStreetType, + CcSuiteNumber, + CcCity, + CcCounty, + CcState, + CcZip, + CcCountry, + CcGmtOffset, + CcTaxPercentage, + ]; + VALUES + } + + /// Get the column type for this column + fn get_column_type(&self) -> &'static ColumnType { + use CallCenterColumn::*; + match self { + CcCallCenterSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CcCallCenterId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(16)) + } + CcRecStartDate => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::date().clone()) + } + CcRecEndDate => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::date().clone()) + } + CcClosedDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcOpenDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + CcClass => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + CcEmployees => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcSqFt => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcHours => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(20)) + } + CcManager => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(40)) + } + CcMktId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcMktClass => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(50)) + } + CcMktDesc => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(100)) + } + CcMarketManager => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(40)) + } + CcDivision => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcDivisionName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + CcCompany => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CcCompanyName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(50)) + } + CcStreetNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CcStreetName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + CcStreetType => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(15)) + } + CcSuiteNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CcCity => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + CcCounty => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(30)) + } + CcState => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(2)) + } + CcZip => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CcCountry => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(20)) + } + CcGmtOffset => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(5, 2)) + } + CcTaxPercentage => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(5, 2)) + } + } + } +} + +impl Column for CallCenterColumn { + fn get_table(&self) -> Table { + Table::CallCenter + } + + fn get_name(&self) -> &'static str { + use CallCenterColumn::*; + match self { + CcCallCenterSk => "cc_call_center_sk", + CcCallCenterId => "cc_call_center_id", + CcRecStartDate => "cc_rec_start_date", + CcRecEndDate => "cc_rec_end_date", + CcClosedDateSk => "cc_closed_date_sk", + CcOpenDateSk => "cc_open_date_sk", + CcName => "cc_name", + CcClass => "cc_class", + CcEmployees => "cc_employees", + CcSqFt => "cc_sq_ft", + CcHours => "cc_hours", + CcManager => "cc_manager", + CcMktId => "cc_mkt_id", + CcMktClass => "cc_mkt_class", + CcMktDesc => "cc_mkt_desc", + CcMarketManager => "cc_market_manager", + CcDivision => "cc_division", + CcDivisionName => "cc_division_name", + CcCompany => "cc_company", + CcCompanyName => "cc_company_name", + CcStreetNumber => "cc_street_number", + CcStreetName => "cc_street_name", + CcStreetType => "cc_street_type", + CcSuiteNumber => "cc_suite_number", + CcCity => "cc_city", + CcCounty => "cc_county", + CcState => "cc_state", + CcZip => "cc_zip", + CcCountry => "cc_country", + CcGmtOffset => "cc_gmt_offset", + CcTaxPercentage => "cc_tax_percentage", + } + } + + fn get_type(&self) -> &ColumnType { + self.get_column_type() + } + + fn get_position(&self) -> i32 { + use CallCenterColumn::*; + match self { + CcCallCenterSk => 0, + CcCallCenterId => 1, + CcRecStartDate => 2, + CcRecEndDate => 3, + CcClosedDateSk => 4, + CcOpenDateSk => 5, + CcName => 6, + CcClass => 7, + CcEmployees => 8, + CcSqFt => 9, + CcHours => 10, + CcManager => 11, + CcMktId => 12, + CcMktClass => 13, + CcMktDesc => 14, + CcMarketManager => 15, + CcDivision => 16, + CcDivisionName => 17, + CcCompany => 18, + CcCompanyName => 19, + CcStreetNumber => 20, + CcStreetName => 21, + CcStreetType => 22, + CcSuiteNumber => 23, + CcCity => 24, + CcCounty => 25, + CcState => 26, + CcZip => 27, + CcCountry => 28, + CcGmtOffset => 29, + CcTaxPercentage => 30, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::column::ColumnTypeBase; + + #[test] + fn test_call_center_column_basics() { + let column = CallCenterColumn::CcCallCenterSk; + assert_eq!(column.get_table(), Table::CallCenter); + assert_eq!(column.get_name(), "cc_call_center_sk"); + assert_eq!(column.get_position(), 0); + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Identifier); + } + + #[test] + fn test_varchar_columns() { + let column = CallCenterColumn::CcName; + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Varchar); + assert_eq!(column.get_type().get_precision(), Some(50)); + assert_eq!(column.get_type().get_scale(), None); + assert_eq!(column.get_type().get_sql_name(), "VARCHAR(50)"); + } + + #[test] + fn test_char_columns() { + let column = CallCenterColumn::CcCallCenterId; + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Char); + assert_eq!(column.get_type().get_precision(), Some(16)); + assert_eq!(column.get_type().get_scale(), None); + assert_eq!(column.get_type().get_sql_name(), "CHAR(16)"); + } + + #[test] + fn test_decimal_columns() { + let column = CallCenterColumn::CcGmtOffset; + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Decimal); + assert_eq!(column.get_type().get_precision(), Some(5)); + assert_eq!(column.get_type().get_scale(), Some(2)); + assert_eq!(column.get_type().get_sql_name(), "DECIMAL(5,2)"); + } + + #[test] + fn test_date_columns() { + let column = CallCenterColumn::CcRecStartDate; + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Date); + assert_eq!(column.get_type().get_precision(), None); + assert_eq!(column.get_type().get_scale(), None); + assert_eq!(column.get_type().get_sql_name(), "DATE"); + } + + #[test] + fn test_integer_columns() { + let column = CallCenterColumn::CcEmployees; + assert_eq!(column.get_type().get_base(), ColumnTypeBase::Integer); + assert_eq!(column.get_type().get_precision(), None); + assert_eq!(column.get_type().get_scale(), None); + assert_eq!(column.get_type().get_sql_name(), "INTEGER"); + } + + #[test] + fn test_all_columns_count() { + let columns = CallCenterColumn::values(); + assert_eq!(columns.len(), 31); + } + + #[test] + fn test_column_positions() { + let columns = CallCenterColumn::values(); + for (index, column) in columns.iter().enumerate() { + assert_eq!(column.get_position(), index as i32); + } + } + + #[test] + fn test_column_names_lowercase() { + for column in CallCenterColumn::values() { + let name = column.get_name(); + assert_eq!(name, name.to_lowercase()); + assert!(name.starts_with("cc_")); + } + } +} diff --git a/tpcdsgen/src/column/column_type.rs b/tpcdsgen/src/column/column_type.rs new file mode 100644 index 00000000..9b30e9a3 --- /dev/null +++ b/tpcdsgen/src/column/column_type.rs @@ -0,0 +1,264 @@ +use crate::{check_state, error::Result, TpcdsError}; + +/// SQL column type base enumeration (ColumnType.Base) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ColumnTypeBase { + Integer, + Identifier, + Date, + Decimal, + Varchar, + Char, + Time, +} + +/// SQL column type with optional precision and scale (ColumnType) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ColumnType { + base: ColumnTypeBase, + precision: Option, + scale: Option, +} + +impl ColumnType { + /// Create a new column type with base, precision, and scale + pub fn new(base: ColumnTypeBase, precision: Option, scale: Option) -> Result { + // Validation matching Java implementation + if base == ColumnTypeBase::Varchar { + check_state!(precision.is_some(), "VARCHAR must have precision"); + } + if base == ColumnTypeBase::Decimal { + check_state!(precision.is_some(), "DECIMAL must have precision"); + check_state!(scale.is_some(), "DECIMAL must have scale"); + } + + Ok(ColumnType { + base, + precision, + scale, + }) + } + + /// Create column type with precision and scale + pub fn with_precision_and_scale( + base: ColumnTypeBase, + precision: i32, + scale: i32, + ) -> Result { + Self::new(base, Some(precision), Some(scale)) + } + + /// Create column type with precision only + pub fn with_precision(base: ColumnTypeBase, precision: i32) -> Result { + Self::new(base, Some(precision), None) + } + + /// Create column type with no precision or scale + pub fn simple(base: ColumnTypeBase) -> Self { + ColumnType { + base, + precision: None, + scale: None, + } + } + + /// Get the base type + pub fn get_base(&self) -> ColumnTypeBase { + self.base + } + + /// Get the precision (if any) + pub fn get_precision(&self) -> Option { + self.precision + } + + /// Get the scale (if any) + pub fn get_scale(&self) -> Option { + self.scale + } + + /// Check if this is a numeric type + pub fn is_numeric(&self) -> bool { + matches!( + self.base, + ColumnTypeBase::Integer | ColumnTypeBase::Identifier | ColumnTypeBase::Decimal + ) + } + + /// Check if this is a string type + pub fn is_string(&self) -> bool { + matches!(self.base, ColumnTypeBase::Varchar | ColumnTypeBase::Char) + } + + /// Check if this is a temporal type + pub fn is_temporal(&self) -> bool { + matches!(self.base, ColumnTypeBase::Date | ColumnTypeBase::Time) + } + + /// Get SQL type name for display purposes + pub fn get_sql_name(&self) -> String { + match self.base { + ColumnTypeBase::Integer => "INTEGER".to_string(), + ColumnTypeBase::Identifier => "IDENTIFIER".to_string(), + ColumnTypeBase::Date => "DATE".to_string(), + ColumnTypeBase::Time => "TIME".to_string(), + ColumnTypeBase::Varchar => { + if let Some(precision) = self.precision { + format!("VARCHAR({})", precision) + } else { + "VARCHAR".to_string() + } + } + ColumnTypeBase::Char => { + if let Some(precision) = self.precision { + format!("CHAR({})", precision) + } else { + "CHAR".to_string() + } + } + ColumnTypeBase::Decimal => match (self.precision, self.scale) { + (Some(p), Some(s)) => format!("DECIMAL({},{})", p, s), + (Some(p), None) => format!("DECIMAL({})", p), + _ => "DECIMAL".to_string(), + }, + } + } +} + +impl std::fmt::Display for ColumnType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.get_sql_name()) + } +} + +impl std::fmt::Display for ColumnTypeBase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + ColumnTypeBase::Integer => "INTEGER", + ColumnTypeBase::Identifier => "IDENTIFIER", + ColumnTypeBase::Date => "DATE", + ColumnTypeBase::Decimal => "DECIMAL", + ColumnTypeBase::Varchar => "VARCHAR", + ColumnTypeBase::Char => "CHAR", + ColumnTypeBase::Time => "TIME", + }; + write!(f, "{}", name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_types() { + let integer_type = ColumnType::simple(ColumnTypeBase::Integer); + assert_eq!(integer_type.get_base(), ColumnTypeBase::Integer); + assert_eq!(integer_type.get_precision(), None); + assert_eq!(integer_type.get_scale(), None); + assert_eq!(integer_type.get_sql_name(), "INTEGER"); + + let date_type = ColumnType::simple(ColumnTypeBase::Date); + assert_eq!(date_type.get_base(), ColumnTypeBase::Date); + assert_eq!(date_type.get_sql_name(), "DATE"); + } + + #[test] + fn test_varchar_with_precision() { + let varchar_type = ColumnType::with_precision(ColumnTypeBase::Varchar, 50).unwrap(); + assert_eq!(varchar_type.get_base(), ColumnTypeBase::Varchar); + assert_eq!(varchar_type.get_precision(), Some(50)); + assert_eq!(varchar_type.get_scale(), None); + assert_eq!(varchar_type.get_sql_name(), "VARCHAR(50)"); + } + + #[test] + fn test_char_with_precision() { + let char_type = ColumnType::with_precision(ColumnTypeBase::Char, 10).unwrap(); + assert_eq!(char_type.get_base(), ColumnTypeBase::Char); + assert_eq!(char_type.get_precision(), Some(10)); + assert_eq!(char_type.get_sql_name(), "CHAR(10)"); + } + + #[test] + fn test_decimal_with_precision_and_scale() { + let decimal_type = + ColumnType::with_precision_and_scale(ColumnTypeBase::Decimal, 10, 2).unwrap(); + assert_eq!(decimal_type.get_base(), ColumnTypeBase::Decimal); + assert_eq!(decimal_type.get_precision(), Some(10)); + assert_eq!(decimal_type.get_scale(), Some(2)); + assert_eq!(decimal_type.get_sql_name(), "DECIMAL(10,2)"); + } + + #[test] + fn test_varchar_validation() { + // VARCHAR must have precision + assert!(ColumnType::new(ColumnTypeBase::Varchar, None, None).is_err()); + assert!(ColumnType::new(ColumnTypeBase::Varchar, Some(50), None).is_ok()); + } + + #[test] + fn test_decimal_validation() { + // DECIMAL must have both precision and scale + assert!(ColumnType::new(ColumnTypeBase::Decimal, None, None).is_err()); + assert!(ColumnType::new(ColumnTypeBase::Decimal, Some(10), None).is_err()); + assert!(ColumnType::new(ColumnTypeBase::Decimal, None, Some(2)).is_err()); + assert!(ColumnType::new(ColumnTypeBase::Decimal, Some(10), Some(2)).is_ok()); + } + + #[test] + fn test_type_classification() { + let integer_type = ColumnType::simple(ColumnTypeBase::Integer); + assert!(integer_type.is_numeric()); + assert!(!integer_type.is_string()); + assert!(!integer_type.is_temporal()); + + let varchar_type = ColumnType::with_precision(ColumnTypeBase::Varchar, 50).unwrap(); + assert!(!varchar_type.is_numeric()); + assert!(varchar_type.is_string()); + assert!(!varchar_type.is_temporal()); + + let date_type = ColumnType::simple(ColumnTypeBase::Date); + assert!(!date_type.is_numeric()); + assert!(!date_type.is_string()); + assert!(date_type.is_temporal()); + + let decimal_type = + ColumnType::with_precision_and_scale(ColumnTypeBase::Decimal, 10, 2).unwrap(); + assert!(decimal_type.is_numeric()); + assert!(!decimal_type.is_string()); + assert!(!decimal_type.is_temporal()); + } + + #[test] + fn test_display() { + assert_eq!( + format!("{}", ColumnType::simple(ColumnTypeBase::Integer)), + "INTEGER" + ); + assert_eq!( + format!( + "{}", + ColumnType::with_precision(ColumnTypeBase::Varchar, 100).unwrap() + ), + "VARCHAR(100)" + ); + assert_eq!( + format!( + "{}", + ColumnType::with_precision_and_scale(ColumnTypeBase::Decimal, 15, 4).unwrap() + ), + "DECIMAL(15,4)" + ); + } + + #[test] + fn test_equality() { + let type1 = ColumnType::with_precision(ColumnTypeBase::Varchar, 50).unwrap(); + let type2 = ColumnType::with_precision(ColumnTypeBase::Varchar, 50).unwrap(); + let type3 = ColumnType::with_precision(ColumnTypeBase::Varchar, 100).unwrap(); + + assert_eq!(type1, type2); + assert_ne!(type1, type3); + } +} diff --git a/tpcdsgen/src/column/column_types.rs b/tpcdsgen/src/column/column_types.rs new file mode 100644 index 00000000..1a8a3180 --- /dev/null +++ b/tpcdsgen/src/column/column_types.rs @@ -0,0 +1,184 @@ +use crate::column::{ColumnType, ColumnTypeBase}; +use std::sync::OnceLock; + +pub struct ColumnTypes; + +impl ColumnTypes { + pub fn integer() -> &'static ColumnType { + static INTEGER: OnceLock = OnceLock::new(); + INTEGER.get_or_init(|| ColumnType::simple(ColumnTypeBase::Integer)) + } + + /// IDENTIFIER type (for surrogate keys) + pub fn identifier() -> &'static ColumnType { + static IDENTIFIER: OnceLock = OnceLock::new(); + IDENTIFIER.get_or_init(|| ColumnType::simple(ColumnTypeBase::Identifier)) + } + + /// DATE type + pub fn date() -> &'static ColumnType { + static DATE: OnceLock = OnceLock::new(); + DATE.get_or_init(|| ColumnType::simple(ColumnTypeBase::Date)) + } + + /// TIME type + pub fn time() -> &'static ColumnType { + static TIME: OnceLock = OnceLock::new(); + TIME.get_or_init(|| ColumnType::simple(ColumnTypeBase::Time)) + } + + /// VARCHAR type with specified precision + pub fn varchar(precision: i32) -> ColumnType { + ColumnType::with_precision(ColumnTypeBase::Varchar, precision) + .expect("VARCHAR type creation should not fail") + } + + /// CHAR type with specified precision + pub fn character(precision: i32) -> ColumnType { + ColumnType::with_precision(ColumnTypeBase::Char, precision) + .expect("CHAR type creation should not fail") + } + + /// DECIMAL type with specified precision and scale + pub fn decimal(precision: i32, scale: i32) -> ColumnType { + ColumnType::with_precision_and_scale(ColumnTypeBase::Decimal, precision, scale) + .expect("DECIMAL type creation should not fail") + } + + /// Create VARCHAR type (convenience alias) + pub fn var_char(precision: i32) -> ColumnType { + Self::varchar(precision) + } + + /// Create CHAR type (convenience alias) + pub fn char(precision: i32) -> ColumnType { + Self::character(precision) + } + + /// Get all standard types for testing/introspection + pub fn standard_types() -> Vec<&'static ColumnType> { + vec![ + Self::integer(), + Self::identifier(), + Self::date(), + Self::time(), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::column::ColumnTypeBase; + + #[test] + fn test_standard_types() { + assert_eq!(ColumnTypes::integer().get_base(), ColumnTypeBase::Integer); + assert_eq!( + ColumnTypes::identifier().get_base(), + ColumnTypeBase::Identifier + ); + assert_eq!(ColumnTypes::date().get_base(), ColumnTypeBase::Date); + assert_eq!(ColumnTypes::time().get_base(), ColumnTypeBase::Time); + } + + #[test] + fn test_varchar_creation() { + let varchar50 = ColumnTypes::varchar(50); + assert_eq!(varchar50.get_base(), ColumnTypeBase::Varchar); + assert_eq!(varchar50.get_precision(), Some(50)); + assert_eq!(varchar50.get_scale(), None); + assert_eq!(varchar50.get_sql_name(), "VARCHAR(50)"); + + let varchar100 = ColumnTypes::var_char(100); + assert_eq!(varchar100.get_precision(), Some(100)); + } + + #[test] + fn test_character_creation() { + let char10 = ColumnTypes::character(10); + assert_eq!(char10.get_base(), ColumnTypeBase::Char); + assert_eq!(char10.get_precision(), Some(10)); + assert_eq!(char10.get_scale(), None); + assert_eq!(char10.get_sql_name(), "CHAR(10)"); + + let char20 = ColumnTypes::char(20); + assert_eq!(char20.get_precision(), Some(20)); + } + + #[test] + fn test_decimal_creation() { + let decimal10_2 = ColumnTypes::decimal(10, 2); + assert_eq!(decimal10_2.get_base(), ColumnTypeBase::Decimal); + assert_eq!(decimal10_2.get_precision(), Some(10)); + assert_eq!(decimal10_2.get_scale(), Some(2)); + assert_eq!(decimal10_2.get_sql_name(), "DECIMAL(10,2)"); + } + + #[test] + fn test_singleton_behavior() { + let int1 = ColumnTypes::integer(); + let int2 = ColumnTypes::integer(); + assert!(std::ptr::eq(int1, int2)); + + let date1 = ColumnTypes::date(); + let date2 = ColumnTypes::date(); + assert!(std::ptr::eq(date1, date2)); + } + + #[test] + fn test_standard_types_collection() { + let types = ColumnTypes::standard_types(); + assert_eq!(types.len(), 4); + + // Verify each type is present + assert!(types + .iter() + .any(|t| t.get_base() == ColumnTypeBase::Integer)); + assert!(types + .iter() + .any(|t| t.get_base() == ColumnTypeBase::Identifier)); + assert!(types.iter().any(|t| t.get_base() == ColumnTypeBase::Date)); + assert!(types.iter().any(|t| t.get_base() == ColumnTypeBase::Time)); + } + + #[test] + fn test_type_display() { + assert_eq!(format!("{}", ColumnTypes::integer()), "INTEGER"); + assert_eq!(format!("{}", ColumnTypes::varchar(255)), "VARCHAR(255)"); + assert_eq!(format!("{}", ColumnTypes::decimal(18, 6)), "DECIMAL(18,6)"); + } + + #[test] + fn test_various_precisions() { + // Test different VARCHAR sizes common in TPC-DS + let small_varchar = ColumnTypes::varchar(16); + let medium_varchar = ColumnTypes::varchar(50); + let large_varchar = ColumnTypes::varchar(100); + + assert_eq!(small_varchar.get_precision(), Some(16)); + assert_eq!(medium_varchar.get_precision(), Some(50)); + assert_eq!(large_varchar.get_precision(), Some(100)); + + // Test different CHAR sizes + let small_char = ColumnTypes::character(1); + let medium_char = ColumnTypes::character(10); + let large_char = ColumnTypes::character(50); + + assert_eq!(small_char.get_precision(), Some(1)); + assert_eq!(medium_char.get_precision(), Some(10)); + assert_eq!(large_char.get_precision(), Some(50)); + + // Test different DECIMAL precisions/scales + let currency_decimal = ColumnTypes::decimal(7, 2); // Common for prices + let percentage_decimal = ColumnTypes::decimal(5, 4); // Common for percentages + let large_decimal = ColumnTypes::decimal(15, 2); // Common for large amounts + + assert_eq!(currency_decimal.get_precision(), Some(7)); + assert_eq!(currency_decimal.get_scale(), Some(2)); + assert_eq!(percentage_decimal.get_precision(), Some(5)); + assert_eq!(percentage_decimal.get_scale(), Some(4)); + assert_eq!(large_decimal.get_precision(), Some(15)); + assert_eq!(large_decimal.get_scale(), Some(2)); + } +} diff --git a/tpcdsgen/src/column/customer_address_column.rs b/tpcdsgen/src/column/customer_address_column.rs new file mode 100644 index 00000000..303eb50c --- /dev/null +++ b/tpcdsgen/src/column/customer_address_column.rs @@ -0,0 +1,166 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer address column definitions (CustomerAddressColumn) + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +/// Customer address column enum (CustomerAddressColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CustomerAddressColumn { + CaAddressSk, + CaAddressId, + CaStreetNumber, + CaStreetName, + CaStreetType, + CaSuiteNumber, + CaCity, + CaCounty, + CaState, + CaZip, + CaCountry, + CaGmtOffset, + CaLocationType, +} + +impl CustomerAddressColumn { + /// Get all column values in order + pub fn values() -> &'static [CustomerAddressColumn] { + use CustomerAddressColumn::*; + static VALUES: &[CustomerAddressColumn] = &[ + CaAddressSk, + CaAddressId, + CaStreetNumber, + CaStreetName, + CaStreetType, + CaSuiteNumber, + CaCity, + CaCounty, + CaState, + CaZip, + CaCountry, + CaGmtOffset, + CaLocationType, + ]; + VALUES + } + + /// Get the column type for this column + fn get_column_type(&self) -> &'static ColumnType { + use CustomerAddressColumn::*; + match self { + CaAddressSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CaAddressId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(16)) + } + CaStreetNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CaStreetName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + CaStreetType => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(15)) + } + CaSuiteNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CaCity => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + CaCounty => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(30)) + } + CaState => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(2)) + } + CaZip => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CaCountry => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(20)) + } + CaGmtOffset => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(5, 2)) + } + CaLocationType => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(20)) + } + } + } +} + +impl Column for CustomerAddressColumn { + fn get_table(&self) -> Table { + Table::CustomerAddress + } + + fn get_name(&self) -> &'static str { + use CustomerAddressColumn::*; + match self { + CaAddressSk => "ca_address_sk", + CaAddressId => "ca_address_id", + CaStreetNumber => "ca_street_number", + CaStreetName => "ca_street_name", + CaStreetType => "ca_street_type", + CaSuiteNumber => "ca_suite_number", + CaCity => "ca_city", + CaCounty => "ca_county", + CaState => "ca_state", + CaZip => "ca_zip", + CaCountry => "ca_country", + CaGmtOffset => "ca_gmt_offset", + CaLocationType => "ca_location_type", + } + } + + fn get_type(&self) -> &ColumnType { + self.get_column_type() + } + + fn get_position(&self) -> i32 { + use CustomerAddressColumn::*; + match self { + CaAddressSk => 0, + CaAddressId => 1, + CaStreetNumber => 2, + CaStreetName => 3, + CaStreetType => 4, + CaSuiteNumber => 5, + CaCity => 6, + CaCounty => 7, + CaState => 8, + CaZip => 9, + CaCountry => 10, + CaGmtOffset => 11, + CaLocationType => 12, + } + } +} diff --git a/tpcdsgen/src/column/customer_column.rs b/tpcdsgen/src/column/customer_column.rs new file mode 100644 index 00000000..db1b63b1 --- /dev/null +++ b/tpcdsgen/src/column/customer_column.rs @@ -0,0 +1,206 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer column definitions (CustomerColumn) + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +/// Customer column enum (CustomerColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CustomerColumn { + CCustomerSk, + CCustomerId, + CCurrentCdemoSk, + CCurrentHdemoSk, + CCurrentAddrSk, + CFirstShiptoDateSk, + CFirstSalesDateSk, + CSalutation, + CFirstName, + CLastName, + CPreferredCustFlag, + CBirthDay, + CBirthMonth, + CBirthYear, + CBirthCountry, + CLogin, + CEmailAddress, + CLastReviewDateSk, +} + +impl CustomerColumn { + /// Get all column values in order + pub fn values() -> &'static [CustomerColumn] { + use CustomerColumn::*; + static VALUES: &[CustomerColumn] = &[ + CCustomerSk, + CCustomerId, + CCurrentCdemoSk, + CCurrentHdemoSk, + CCurrentAddrSk, + CFirstShiptoDateSk, + CFirstSalesDateSk, + CSalutation, + CFirstName, + CLastName, + CPreferredCustFlag, + CBirthDay, + CBirthMonth, + CBirthYear, + CBirthCountry, + CLogin, + CEmailAddress, + CLastReviewDateSk, + ]; + VALUES + } + + /// Get the column type for this column + fn get_column_type(&self) -> &'static ColumnType { + use CustomerColumn::*; + match self { + CCustomerSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CCustomerId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(16)) + } + CCurrentCdemoSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CCurrentHdemoSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CCurrentAddrSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CFirstShiptoDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CFirstSalesDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + CSalutation => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + CFirstName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(20)) + } + CLastName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(30)) + } + CPreferredCustFlag => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + CBirthDay => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CBirthMonth => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CBirthYear => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + CBirthCountry => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(20)) + } + CLogin => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(13)) + } + CEmailAddress => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(50)) + } + CLastReviewDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + } + } +} + +impl Column for CustomerColumn { + fn get_table(&self) -> Table { + Table::Customer + } + + fn get_name(&self) -> &'static str { + use CustomerColumn::*; + match self { + CCustomerSk => "c_customer_sk", + CCustomerId => "c_customer_id", + CCurrentCdemoSk => "c_current_cdemo_sk", + CCurrentHdemoSk => "c_current_hdemo_sk", + CCurrentAddrSk => "c_current_addr_sk", + CFirstShiptoDateSk => "c_first_shipto_date_sk", + CFirstSalesDateSk => "c_first_sales_date_sk", + CSalutation => "c_salutation", + CFirstName => "c_first_name", + CLastName => "c_last_name", + CPreferredCustFlag => "c_preferred_cust_flag", + CBirthDay => "c_birth_day", + CBirthMonth => "c_birth_month", + CBirthYear => "c_birth_year", + CBirthCountry => "c_birth_country", + CLogin => "c_login", + CEmailAddress => "c_email_address", + CLastReviewDateSk => "c_last_review_date_sk", + } + } + + fn get_type(&self) -> &ColumnType { + self.get_column_type() + } + + fn get_position(&self) -> i32 { + use CustomerColumn::*; + match self { + CCustomerSk => 0, + CCustomerId => 1, + CCurrentCdemoSk => 2, + CCurrentHdemoSk => 3, + CCurrentAddrSk => 4, + CFirstShiptoDateSk => 5, + CFirstSalesDateSk => 6, + CSalutation => 7, + CFirstName => 8, + CLastName => 9, + CPreferredCustFlag => 10, + CBirthDay => 11, + CBirthMonth => 12, + CBirthYear => 13, + CBirthCountry => 14, + CLogin => 15, + CEmailAddress => 16, + CLastReviewDateSk => 17, + } + } +} diff --git a/tpcdsgen/src/column/dbgen_version.rs b/tpcdsgen/src/column/dbgen_version.rs new file mode 100644 index 00000000..d4f678f9 --- /dev/null +++ b/tpcdsgen/src/column/dbgen_version.rs @@ -0,0 +1,140 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DbgenVersionColumn { + DvVersion, + DvCreateDate, + DvCreateTime, + DvCmdlineArgs, +} + +impl DbgenVersionColumn { + /// Get all column values in order + pub fn values() -> &'static [DbgenVersionColumn] { + &[ + DbgenVersionColumn::DvVersion, + DbgenVersionColumn::DvCreateDate, + DbgenVersionColumn::DvCreateTime, + DbgenVersionColumn::DvCmdlineArgs, + ] + } +} + +impl Column for DbgenVersionColumn { + fn get_table(&self) -> Table { + Table::DbgenVersion + } + + fn get_name(&self) -> &'static str { + match self { + DbgenVersionColumn::DvVersion => "dv_version", + DbgenVersionColumn::DvCreateDate => "dv_create_date", + DbgenVersionColumn::DvCreateTime => "dv_create_time", + DbgenVersionColumn::DvCmdlineArgs => "dv_cmdline_args", + } + } + + fn get_type(&self) -> &ColumnType { + use DbgenVersionColumn::*; + match self { + DvVersion => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(16)) + } + DvCreateDate => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::date().clone()) + } + DvCreateTime => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::time().clone()) + } + DvCmdlineArgs => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(200)) + } + } + } + + fn get_position(&self) -> i32 { + match self { + DbgenVersionColumn::DvVersion => 0, + DbgenVersionColumn::DvCreateDate => 1, + DbgenVersionColumn::DvCreateTime => 2, + DbgenVersionColumn::DvCmdlineArgs => 3, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::column::ColumnTypeBase; + + #[test] + fn test_dbgen_version_column_count() { + assert_eq!(DbgenVersionColumn::values().len(), 4); + } + + #[test] + fn test_dbgen_version_column_names() { + assert_eq!(DbgenVersionColumn::DvVersion.get_name(), "dv_version"); + assert_eq!( + DbgenVersionColumn::DvCreateDate.get_name(), + "dv_create_date" + ); + assert_eq!( + DbgenVersionColumn::DvCreateTime.get_name(), + "dv_create_time" + ); + assert_eq!( + DbgenVersionColumn::DvCmdlineArgs.get_name(), + "dv_cmdline_args" + ); + } + + #[test] + fn test_dbgen_version_column_types() { + assert_eq!( + DbgenVersionColumn::DvVersion.get_type().get_base(), + ColumnTypeBase::Varchar + ); + assert_eq!( + DbgenVersionColumn::DvCreateDate.get_type().get_base(), + ColumnTypeBase::Date + ); + assert_eq!( + DbgenVersionColumn::DvCreateTime.get_type().get_base(), + ColumnTypeBase::Time + ); + } + + #[test] + fn test_dbgen_version_column_positions() { + assert_eq!(DbgenVersionColumn::DvVersion.get_position(), 0); + assert_eq!(DbgenVersionColumn::DvCmdlineArgs.get_position(), 3); + } + + #[test] + fn test_dbgen_version_column_table() { + assert_eq!( + DbgenVersionColumn::DvVersion.get_table(), + Table::DbgenVersion + ); + } +} diff --git a/tpcdsgen/src/column/household_demographics.rs b/tpcdsgen/src/column/household_demographics.rs new file mode 100644 index 00000000..e8a90d7b --- /dev/null +++ b/tpcdsgen/src/column/household_demographics.rs @@ -0,0 +1,91 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +/// Household Demographics table columns (HouseholdDemographicsColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum HouseholdDemographicsColumn { + HdDemoSk, + HdIncomeBandSk, + HdBuyPotential, + HdDepCount, + HdVehicleCount, +} + +impl HouseholdDemographicsColumn { + /// Get all columns in order + pub fn values() -> &'static [HouseholdDemographicsColumn] { + use HouseholdDemographicsColumn::*; + static VALUES: &[HouseholdDemographicsColumn] = &[ + HdDemoSk, + HdIncomeBandSk, + HdBuyPotential, + HdDepCount, + HdVehicleCount, + ]; + VALUES + } + + /// Get the column type for this column + fn get_column_type(&self) -> &'static ColumnType { + match self { + HouseholdDemographicsColumn::HdDemoSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + HouseholdDemographicsColumn::HdIncomeBandSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + HouseholdDemographicsColumn::HdBuyPotential => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(15)) + } + HouseholdDemographicsColumn::HdDepCount => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + HouseholdDemographicsColumn::HdVehicleCount => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + } + } +} + +impl Column for HouseholdDemographicsColumn { + fn get_table(&self) -> Table { + Table::HouseholdDemographics + } + + fn get_name(&self) -> &'static str { + match self { + HouseholdDemographicsColumn::HdDemoSk => "hd_demo_sk", + HouseholdDemographicsColumn::HdIncomeBandSk => "hd_income_band_sk", + HouseholdDemographicsColumn::HdBuyPotential => "hd_buy_potential", + HouseholdDemographicsColumn::HdDepCount => "hd_dep_count", + HouseholdDemographicsColumn::HdVehicleCount => "hd_vehicle_count", + } + } + + fn get_type(&self) -> &ColumnType { + self.get_column_type() + } + + fn get_position(&self) -> i32 { + *self as i32 + } +} diff --git a/tpcdsgen/src/column/inventory_column.rs b/tpcdsgen/src/column/inventory_column.rs new file mode 100644 index 00000000..5e87a6ff --- /dev/null +++ b/tpcdsgen/src/column/inventory_column.rs @@ -0,0 +1,69 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Inventory column definitions + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; + +/// Columns for the inventory table. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InventoryColumn { + InvDateSk, + InvItemSk, + InvWarehouseSk, + InvQuantityOnHand, +} + +impl InventoryColumn { + /// Get all column variants in order + pub fn values() -> &'static [InventoryColumn] { + use InventoryColumn::*; + static COLUMNS: [InventoryColumn; 4] = + [InvDateSk, InvItemSk, InvWarehouseSk, InvQuantityOnHand]; + &COLUMNS + } +} + +impl Column for InventoryColumn { + fn get_table(&self) -> Table { + Table::Inventory + } + + fn get_name(&self) -> &'static str { + match self { + InventoryColumn::InvDateSk => "inv_date_sk", + InventoryColumn::InvItemSk => "inv_item_sk", + InventoryColumn::InvWarehouseSk => "inv_warehouse_sk", + InventoryColumn::InvQuantityOnHand => "inv_quantity_on_hand", + } + } + + fn get_type(&self) -> &ColumnType { + match self { + InventoryColumn::InvDateSk => ColumnTypes::identifier(), + InventoryColumn::InvItemSk => ColumnTypes::identifier(), + InventoryColumn::InvWarehouseSk => ColumnTypes::identifier(), + InventoryColumn::InvQuantityOnHand => ColumnTypes::integer(), + } + } + + fn get_position(&self) -> i32 { + match self { + InventoryColumn::InvDateSk => 0, + InventoryColumn::InvItemSk => 1, + InventoryColumn::InvWarehouseSk => 2, + InventoryColumn::InvQuantityOnHand => 3, + } + } +} diff --git a/tpcdsgen/src/column/mod.rs b/tpcdsgen/src/column/mod.rs new file mode 100644 index 00000000..503767b5 --- /dev/null +++ b/tpcdsgen/src/column/mod.rs @@ -0,0 +1,53 @@ +pub mod call_center; +pub mod column_type; +pub mod column_types; +pub mod customer_address_column; +pub mod customer_column; +pub mod dbgen_version; +pub mod household_demographics; +pub mod inventory_column; +pub mod promotion; +pub mod web_site; + +pub use call_center::CallCenterColumn; +pub use column_type::{ColumnType, ColumnTypeBase}; +pub use column_types::ColumnTypes; +pub use customer_address_column::CustomerAddressColumn; +pub use customer_column::CustomerColumn; +pub use dbgen_version::DbgenVersionColumn; +pub use household_demographics::HouseholdDemographicsColumn; +pub use inventory_column::InventoryColumn; +pub use promotion::PromotionColumn; +pub use web_site::WebSiteColumn; + +// Re-export Table from crate::table to provide a single source of truth +// This eliminates the duplicate Table enum that previously existed here +pub use crate::table::Table; + +/// Column trait for TPC-DS table columns. +/// Used by column enums (CallCenterColumn, CustomerColumn, etc.) to provide +/// common functionality for column metadata access. +pub trait Column: Send + Sync { + /// Get the table this column belongs to + fn get_table(&self) -> Table; + + /// Get the column name (lowercase) + fn get_name(&self) -> &'static str; + + /// Get the column type + fn get_type(&self) -> &ColumnType; + + /// Get the column position (0-based ordinal) + fn get_position(&self) -> i32; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_table_name() { + assert_eq!(Table::CallCenter.get_name(), "call_center"); + assert_eq!(format!("{}", Table::CallCenter), "call_center"); + } +} diff --git a/tpcdsgen/src/column/promotion.rs b/tpcdsgen/src/column/promotion.rs new file mode 100644 index 00000000..3889c278 --- /dev/null +++ b/tpcdsgen/src/column/promotion.rs @@ -0,0 +1,249 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PromotionColumn { + PPromoSk, + PPromoId, + PStartDateSk, + PEndDateSk, + PItemSk, + PCost, + PResponseTarge, // Note: Matches Java typo (missing 'T') + PPromoName, + PChannelDmail, + PChannelEmail, + PChannelCatalog, + PChannelTv, + PChannelRadio, + PChannelPress, + PChannelEvent, + PChannelDemo, + PChannelDetails, + PPurpose, + PDiscountActive, +} + +impl PromotionColumn { + /// Get all column values in order + pub fn values() -> &'static [PromotionColumn] { + &[ + PromotionColumn::PPromoSk, + PromotionColumn::PPromoId, + PromotionColumn::PStartDateSk, + PromotionColumn::PEndDateSk, + PromotionColumn::PItemSk, + PromotionColumn::PCost, + PromotionColumn::PResponseTarge, + PromotionColumn::PPromoName, + PromotionColumn::PChannelDmail, + PromotionColumn::PChannelEmail, + PromotionColumn::PChannelCatalog, + PromotionColumn::PChannelTv, + PromotionColumn::PChannelRadio, + PromotionColumn::PChannelPress, + PromotionColumn::PChannelEvent, + PromotionColumn::PChannelDemo, + PromotionColumn::PChannelDetails, + PromotionColumn::PPurpose, + PromotionColumn::PDiscountActive, + ] + } +} + +impl Column for PromotionColumn { + fn get_table(&self) -> Table { + Table::Promotion + } + + fn get_name(&self) -> &'static str { + match self { + PromotionColumn::PPromoSk => "p_promo_sk", + PromotionColumn::PPromoId => "p_promo_id", + PromotionColumn::PStartDateSk => "p_start_date_sk", + PromotionColumn::PEndDateSk => "p_end_date_sk", + PromotionColumn::PItemSk => "p_item_sk", + PromotionColumn::PCost => "p_cost", + PromotionColumn::PResponseTarge => "p_response_targe", // Matches Java typo + PromotionColumn::PPromoName => "p_promo_name", + PromotionColumn::PChannelDmail => "p_channel_dmail", + PromotionColumn::PChannelEmail => "p_channel_email", + PromotionColumn::PChannelCatalog => "p_channel_catalog", + PromotionColumn::PChannelTv => "p_channel_tv", + PromotionColumn::PChannelRadio => "p_channel_radio", + PromotionColumn::PChannelPress => "p_channel_press", + PromotionColumn::PChannelEvent => "p_channel_event", + PromotionColumn::PChannelDemo => "p_channel_demo", + PromotionColumn::PChannelDetails => "p_channel_details", + PromotionColumn::PPurpose => "p_purpose", + PromotionColumn::PDiscountActive => "p_discount_active", + } + } + + fn get_type(&self) -> &ColumnType { + use PromotionColumn::*; + match self { + PPromoSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + PPromoId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(16)) + } + PStartDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + PEndDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + PItemSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + PCost => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(15, 2)) + } + PResponseTarge => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + PPromoName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(50)) + } + PChannelDmail => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelEmail => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelCatalog => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelTv => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelRadio => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelPress => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelEvent => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelDemo => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + PChannelDetails => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(100)) + } + PPurpose => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(15)) + } + PDiscountActive => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(1)) + } + } + } + + fn get_position(&self) -> i32 { + match self { + PromotionColumn::PPromoSk => 0, + PromotionColumn::PPromoId => 1, + PromotionColumn::PStartDateSk => 2, + PromotionColumn::PEndDateSk => 3, + PromotionColumn::PItemSk => 4, + PromotionColumn::PCost => 5, + PromotionColumn::PResponseTarge => 6, + PromotionColumn::PPromoName => 7, + PromotionColumn::PChannelDmail => 8, + PromotionColumn::PChannelEmail => 9, + PromotionColumn::PChannelCatalog => 10, + PromotionColumn::PChannelTv => 11, + PromotionColumn::PChannelRadio => 12, + PromotionColumn::PChannelPress => 13, + PromotionColumn::PChannelEvent => 14, + PromotionColumn::PChannelDemo => 15, + PromotionColumn::PChannelDetails => 16, + PromotionColumn::PPurpose => 17, + PromotionColumn::PDiscountActive => 18, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::column::ColumnTypeBase; + + #[test] + fn test_promotion_column_count() { + assert_eq!(PromotionColumn::values().len(), 19); + } + + #[test] + fn test_promotion_column_names() { + assert_eq!(PromotionColumn::PPromoSk.get_name(), "p_promo_sk"); + assert_eq!(PromotionColumn::PPromoId.get_name(), "p_promo_id"); + // Test typo is preserved + assert_eq!( + PromotionColumn::PResponseTarge.get_name(), + "p_response_targe" + ); + } + + #[test] + fn test_promotion_column_types() { + assert_eq!( + PromotionColumn::PPromoSk.get_type().get_base(), + ColumnTypeBase::Identifier + ); + assert_eq!( + PromotionColumn::PCost.get_type().get_base(), + ColumnTypeBase::Decimal + ); + assert_eq!(PromotionColumn::PCost.get_type().get_precision(), Some(15)); + assert_eq!(PromotionColumn::PCost.get_type().get_scale(), Some(2)); + } + + #[test] + fn test_promotion_column_positions() { + assert_eq!(PromotionColumn::PPromoSk.get_position(), 0); + assert_eq!(PromotionColumn::PDiscountActive.get_position(), 18); + } + + #[test] + fn test_promotion_column_table() { + assert_eq!(PromotionColumn::PPromoSk.get_table(), Table::Promotion); + } +} diff --git a/tpcdsgen/src/column/web_site.rs b/tpcdsgen/src/column/web_site.rs new file mode 100644 index 00000000..2374df86 --- /dev/null +++ b/tpcdsgen/src/column/web_site.rs @@ -0,0 +1,283 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::{Column, ColumnType, ColumnTypes, Table}; +use std::sync::OnceLock; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WebSiteColumn { + WebSiteSk, + WebSiteId, + WebRecStartDate, + WebRecEndDate, + WebName, + WebOpenDateSk, + WebCloseDateSk, + WebClass, + WebManager, + WebMktId, + WebMktClass, + WebMktDesc, + WebMarketManager, + WebCompanyId, + WebCompanyName, + WebStreetNumber, + WebStreetName, + WebStreetType, + WebSuiteNumber, + WebCity, + WebCounty, + WebState, + WebZip, + WebCountry, + WebGmtOffset, + WebTaxPercentage, +} + +impl WebSiteColumn { + pub fn values() -> &'static [WebSiteColumn] { + &[ + WebSiteColumn::WebSiteSk, + WebSiteColumn::WebSiteId, + WebSiteColumn::WebRecStartDate, + WebSiteColumn::WebRecEndDate, + WebSiteColumn::WebName, + WebSiteColumn::WebOpenDateSk, + WebSiteColumn::WebCloseDateSk, + WebSiteColumn::WebClass, + WebSiteColumn::WebManager, + WebSiteColumn::WebMktId, + WebSiteColumn::WebMktClass, + WebSiteColumn::WebMktDesc, + WebSiteColumn::WebMarketManager, + WebSiteColumn::WebCompanyId, + WebSiteColumn::WebCompanyName, + WebSiteColumn::WebStreetNumber, + WebSiteColumn::WebStreetName, + WebSiteColumn::WebStreetType, + WebSiteColumn::WebSuiteNumber, + WebSiteColumn::WebCity, + WebSiteColumn::WebCounty, + WebSiteColumn::WebState, + WebSiteColumn::WebZip, + WebSiteColumn::WebCountry, + WebSiteColumn::WebGmtOffset, + WebSiteColumn::WebTaxPercentage, + ] + } +} + +impl Column for WebSiteColumn { + fn get_table(&self) -> Table { + Table::WebSite + } + + fn get_name(&self) -> &'static str { + match self { + WebSiteColumn::WebSiteSk => "web_site_sk", + WebSiteColumn::WebSiteId => "web_site_id", + WebSiteColumn::WebRecStartDate => "web_rec_start_date", + WebSiteColumn::WebRecEndDate => "web_rec_end_date", + WebSiteColumn::WebName => "web_name", + WebSiteColumn::WebOpenDateSk => "web_open_date_sk", + WebSiteColumn::WebCloseDateSk => "web_close_date_sk", + WebSiteColumn::WebClass => "web_class", + WebSiteColumn::WebManager => "web_manager", + WebSiteColumn::WebMktId => "web_mkt_id", + WebSiteColumn::WebMktClass => "web_mkt_class", + WebSiteColumn::WebMktDesc => "web_mkt_desc", + WebSiteColumn::WebMarketManager => "web_market_manager", + WebSiteColumn::WebCompanyId => "web_company_id", + WebSiteColumn::WebCompanyName => "web_company_name", + WebSiteColumn::WebStreetNumber => "web_street_number", + WebSiteColumn::WebStreetName => "web_street_name", + WebSiteColumn::WebStreetType => "web_street_type", + WebSiteColumn::WebSuiteNumber => "web_suite_number", + WebSiteColumn::WebCity => "web_city", + WebSiteColumn::WebCounty => "web_county", + WebSiteColumn::WebState => "web_state", + WebSiteColumn::WebZip => "web_zip", + WebSiteColumn::WebCountry => "web_country", + WebSiteColumn::WebGmtOffset => "web_gmt_offset", + WebSiteColumn::WebTaxPercentage => "web_tax_percentage", + } + } + + fn get_type(&self) -> &ColumnType { + use WebSiteColumn::*; + match self { + WebSiteSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + WebSiteId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(16)) + } + WebRecStartDate => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::date().clone()) + } + WebRecEndDate => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::date().clone()) + } + WebName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + WebOpenDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + WebCloseDateSk => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::identifier().clone()) + } + WebClass => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + WebManager => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(40)) + } + WebMktId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + WebMktClass => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(50)) + } + WebMktDesc => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(100)) + } + WebMarketManager => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(40)) + } + WebCompanyId => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::integer().clone()) + } + WebCompanyName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(50)) + } + WebStreetNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + WebStreetName => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + WebStreetType => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(15)) + } + WebSuiteNumber => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + WebCity => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(60)) + } + WebCounty => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(30)) + } + WebState => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(2)) + } + WebZip => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::character(10)) + } + WebCountry => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::varchar(20)) + } + WebGmtOffset => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(5, 2)) + } + WebTaxPercentage => { + static TYPE: OnceLock = OnceLock::new(); + TYPE.get_or_init(|| ColumnTypes::decimal(5, 2)) + } + } + } + + fn get_position(&self) -> i32 { + match self { + WebSiteColumn::WebSiteSk => 0, + WebSiteColumn::WebSiteId => 1, + WebSiteColumn::WebRecStartDate => 2, + WebSiteColumn::WebRecEndDate => 3, + WebSiteColumn::WebName => 4, + WebSiteColumn::WebOpenDateSk => 5, + WebSiteColumn::WebCloseDateSk => 6, + WebSiteColumn::WebClass => 7, + WebSiteColumn::WebManager => 8, + WebSiteColumn::WebMktId => 9, + WebSiteColumn::WebMktClass => 10, + WebSiteColumn::WebMktDesc => 11, + WebSiteColumn::WebMarketManager => 12, + WebSiteColumn::WebCompanyId => 13, + WebSiteColumn::WebCompanyName => 14, + WebSiteColumn::WebStreetNumber => 15, + WebSiteColumn::WebStreetName => 16, + WebSiteColumn::WebStreetType => 17, + WebSiteColumn::WebSuiteNumber => 18, + WebSiteColumn::WebCity => 19, + WebSiteColumn::WebCounty => 20, + WebSiteColumn::WebState => 21, + WebSiteColumn::WebZip => 22, + WebSiteColumn::WebCountry => 23, + WebSiteColumn::WebGmtOffset => 24, + WebSiteColumn::WebTaxPercentage => 25, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_web_site_column_count() { + assert_eq!(WebSiteColumn::values().len(), 26); + } + + #[test] + fn test_web_site_column_names() { + assert_eq!(WebSiteColumn::WebSiteSk.get_name(), "web_site_sk"); + assert_eq!(WebSiteColumn::WebSiteId.get_name(), "web_site_id"); + assert_eq!( + WebSiteColumn::WebRecStartDate.get_name(), + "web_rec_start_date" + ); + } + + #[test] + fn test_web_site_column_positions() { + assert_eq!(WebSiteColumn::WebSiteSk.get_position(), 0); + assert_eq!(WebSiteColumn::WebTaxPercentage.get_position(), 25); + } +} diff --git a/tpcdsgen/src/config/mod.rs b/tpcdsgen/src/config/mod.rs new file mode 100644 index 00000000..d182211d --- /dev/null +++ b/tpcdsgen/src/config/mod.rs @@ -0,0 +1,9 @@ +pub mod options; +pub mod scaling; +pub mod session; +pub mod table; + +pub use options::Options; +pub use scaling::Scaling; +pub use session::Session; +pub use table::Table; diff --git a/tpcdsgen/src/config/options.rs b/tpcdsgen/src/config/options.rs new file mode 100644 index 00000000..5817cb93 --- /dev/null +++ b/tpcdsgen/src/config/options.rs @@ -0,0 +1,300 @@ +use crate::config::{Session, Table}; +use crate::error::{InvalidOptionError, Result}; +use clap::Parser; + +#[derive(Parser, Debug, Clone)] +#[command(name = "tpcdsgen")] +#[command(about = "Rust implementation of TPC-DS data generator")] +pub struct Options { + /// Volume of data to generate in GB (Default: 1) + #[arg(long = "scale", short = 's', default_value = "1")] + pub scale: f64, + + /// Directory to put generated files (Default: .) + #[arg(long = "directory", short = 'd', default_value = ".")] + pub directory: String, + + /// Suffix for generated data files (Default: .dat) + #[arg(long = "suffix", default_value = ".dat")] + pub suffix: String, + + /// Build only the specified table. If not specified, all tables will be generated + #[arg(long = "table", short = 't')] + pub table: Option, + + /// String representation for null values (Default: the empty string) + #[arg(long = "null", default_value = "")] + pub null_string: String, + + /// Separator between columns (Default: |) + #[arg(long = "separator", default_value = "|")] + pub separator: String, + + /// Do not terminate each row with a separator (Default: false) + #[arg(long = "do-not-terminate")] + pub do_not_terminate: bool, + + /// Use gender-neutral manager names. + /// This diverges from C implementation but is supported by the Java one (i need to check the latest spec) + #[arg(long = "no-sexism")] + pub no_sexism: bool, + + /// Build data in `n` separate chunks (Default: 1) + #[arg(long = "parallelism", default_value = "1")] + pub parallelism: i32, + + /// Overwrite existing data files for tables + #[arg(long = "overwrite")] + pub overwrite: bool, +} + +impl Options { + // Default constants (matching Java implementation) + pub const DEFAULT_SCALE: f64 = 1.0; + pub const DEFAULT_DIRECTORY: &'static str = "."; + pub const DEFAULT_SUFFIX: &'static str = ".dat"; + pub const DEFAULT_NULL_STRING: &'static str = ""; + pub const DEFAULT_SEPARATOR: char = '|'; + pub const DEFAULT_DO_NOT_TERMINATE: bool = false; + pub const DEFAULT_NO_SEXISM: bool = false; + pub const DEFAULT_PARALLELISM: i32 = 1; + pub const DEFAULT_OVERWRITE: bool = false; + + pub fn new() -> Self { + Self { + scale: Self::DEFAULT_SCALE, + directory: Self::DEFAULT_DIRECTORY.to_string(), + suffix: Self::DEFAULT_SUFFIX.to_string(), + table: None, + null_string: Self::DEFAULT_NULL_STRING.to_string(), + separator: Self::DEFAULT_SEPARATOR.to_string(), + do_not_terminate: Self::DEFAULT_DO_NOT_TERMINATE, + no_sexism: Self::DEFAULT_NO_SEXISM, + parallelism: Self::DEFAULT_PARALLELISM, + overwrite: Self::DEFAULT_OVERWRITE, + } + } + + /// Convert Options to Session, performing validation + pub fn to_session(&self) -> Result { + self.validate_properties()?; + + let table_option = if let Some(table_str) = &self.table { + Some(self.parse_table(table_str)?) + } else { + None + }; + + // Parse separator (should be single character) + let separator_char = if self.separator.len() == 1 { + self.separator.chars().next().unwrap() + } else { + return Err(InvalidOptionError::with_message( + "separator", + &self.separator, + "Separator must be a single character", + ) + .into()); + }; + + Ok(Session::new( + self.scale, + self.directory.clone(), + self.suffix.clone(), + table_option, + self.null_string.clone(), + separator_char, + self.do_not_terminate, + self.no_sexism, + self.parallelism, + self.overwrite, + )) + } + + /// Parse table name to Table enum (case-insensitive) + fn parse_table(&self, table_str: &str) -> Result
{ + table_str + .parse::
() + .map_err(|_| InvalidOptionError::new("table", table_str).into()) + } + + /// Validate all properties (matching Java validation rules) + fn validate_properties(&self) -> Result<()> { + // Scale validation + if self.scale < 0.0 || self.scale > 100000.0 { + return Err(InvalidOptionError::with_message( + "scale", + &self.scale.to_string(), + "Scale must be greater than 0 and less than 100000", + ) + .into()); + } + + // Directory validation + if self.directory.is_empty() { + return Err(InvalidOptionError::with_message( + "directory", + &self.directory, + "Directory cannot be an empty string", + ) + .into()); + } + + // Suffix validation + if self.suffix.is_empty() { + return Err(InvalidOptionError::with_message( + "suffix", + &self.suffix, + "Suffix cannot be an empty string", + ) + .into()); + } + + // Parallelism validation + if self.parallelism < 1 { + return Err(InvalidOptionError::with_message( + "parallelism", + &self.parallelism.to_string(), + "Parallelism must be >= 1", + ) + .into()); + } + + // Separator validation + if self.separator.len() != 1 { + return Err(InvalidOptionError::with_message( + "separator", + &self.separator, + "Separator must be a single character", + ) + .into()); + } + + Ok(()) + } +} + +impl Default for Options { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_options_defaults() { + let options = Options::new(); + assert_eq!(options.scale, 1.0); + assert_eq!(options.directory, "."); + assert_eq!(options.suffix, ".dat"); + assert_eq!(options.table, None); + assert_eq!(options.null_string, ""); + assert_eq!(options.separator, "|"); + assert!(!options.do_not_terminate); + assert!(!options.no_sexism); + assert_eq!(options.parallelism, 1); + assert!(!options.overwrite); + } + + #[test] + fn test_valid_options_to_session() { + let options = Options::new(); + let session = options.to_session().unwrap(); + assert_eq!(session.get_scaling().get_scale(), 1.0); + assert_eq!(session.get_target_directory(), "."); + assert_eq!(session.get_suffix(), ".dat"); + assert!(!session.generate_only_one_table()); + } + + #[test] + fn test_table_parsing() { + let mut options = Options::new(); + options.table = Some("catalog_sales".to_string()); + let session = options.to_session().unwrap(); + assert!(session.generate_only_one_table()); + assert_eq!(session.get_only_table_to_generate(), Table::CatalogSales); + } + + #[test] + fn test_invalid_table() { + let mut options = Options::new(); + options.table = Some("invalid_table".to_string()); + assert!(options.to_session().is_err()); + } + + #[test] + fn test_scale_validation() { + let mut options = Options::new(); + + // Valid scale + options.scale = 10.0; + assert!(options.validate_properties().is_ok()); + + // Invalid scale - too large + options.scale = 200000.0; + assert!(options.validate_properties().is_err()); + + // Invalid scale - negative + options.scale = -1.0; + assert!(options.validate_properties().is_err()); + } + + #[test] + fn test_directory_validation() { + let mut options = Options::new(); + + // Valid directory + options.directory = "/tmp".to_string(); + assert!(options.validate_properties().is_ok()); + + // Invalid directory - empty + options.directory = "".to_string(); + assert!(options.validate_properties().is_err()); + } + + #[test] + fn test_suffix_validation() { + let mut options = Options::new(); + + // Valid suffix + options.suffix = ".csv".to_string(); + assert!(options.validate_properties().is_ok()); + + // Invalid suffix - empty + options.suffix = "".to_string(); + assert!(options.validate_properties().is_err()); + } + + #[test] + fn test_parallelism_validation() { + let mut options = Options::new(); + + // Valid parallelism + options.parallelism = 4; + assert!(options.validate_properties().is_ok()); + + // Invalid parallelism - too small + options.parallelism = 0; + assert!(options.validate_properties().is_err()); + } + + #[test] + fn test_separator_validation() { + let mut options = Options::new(); + + // Valid separator + options.separator = ",".to_string(); + assert!(options.validate_properties().is_ok()); + + // Invalid separator - too long + options.separator = "||".to_string(); + assert!(options.validate_properties().is_err()); + + // Invalid separator - empty + options.separator = "".to_string(); + assert!(options.validate_properties().is_err()); + } +} diff --git a/tpcdsgen/src/config/scaling.rs b/tpcdsgen/src/config/scaling.rs new file mode 100644 index 00000000..47dfa3a8 --- /dev/null +++ b/tpcdsgen/src/config/scaling.rs @@ -0,0 +1,277 @@ +use crate::config::Table; +use crate::distribution::calendar_distribution::{CalendarDistribution, CalendarWeights}; +use crate::table::Table as MetaTable; +use crate::types::Date; + +#[derive(Debug, Clone)] +pub struct Scaling { + scale: f64, +} + +impl Scaling { + pub fn new(scale: f64) -> Self { + Scaling { scale } + } + + pub fn get_scale(&self) -> f64 { + self.scale + } + + /// Get row count for a table at this scale factor. + /// + /// Uses the table's ScalingInfo to properly calculate row counts based on + /// the scaling model (Static, Linear, or Logarithmic). + /// + /// Note: Inventory is a special case - its row count is computed dynamically + /// as item_id_count × warehouse_count × weeks (matching Java's scaleInventory()). + pub fn get_row_count(&self, table: Table) -> i64 { + // Special case for Inventory - computed dynamically like Java's scaleInventory() + // See: Java Scaling.java getRowCount() and scaleInventory() + if table == Table::Inventory { + return self.scale_inventory(); + } + + // Convert config::Table to table::Table to access ScalingInfo + let meta_table = Self::to_meta_table(table); + + // Get base row count from ScalingInfo + let scaling_info = meta_table.get_scaling_info(); + let base_row_count = scaling_info + .get_row_count_for_scale(self.scale) + .unwrap_or(0); + + // Apply multiplier based on keepsHistory and scalingInfo.multiplier + // multiplier = (keepsHistory ? 2 : 1) * 10^scalingInfo.multiplier + let mut multiplier: i64 = if meta_table.keeps_history() { 2 } else { 1 }; + for _ in 0..scaling_info.get_multiplier() { + multiplier *= 10; + } + + base_row_count * multiplier + } + + /// Compute inventory row count dynamically. + /// + /// Inventory row count = item_id_count × warehouse_count × weeks + /// This matches Java's Scaling.scaleInventory() method exactly. + /// + /// From Java: + /// ```java + /// private long scaleInventory() { + /// int nDays = JULIAN_DATE_MAXIMUM - JULIAN_DATE_MINIMUM; + /// nDays += 7; // ndays + 1 + 6 + /// nDays /= 7; // each item's inventory is updated weekly + /// return getIdCount(ITEM) * getRowCount(WAREHOUSE) * nDays; + /// } + /// ``` + fn scale_inventory(&self) -> i64 { + let n_days = Date::JULIAN_DATE_MAXIMUM - Date::JULIAN_DATE_MINIMUM; + let n_weeks = (n_days + 7) / 7; // Round up to weeks + self.get_id_count(Table::Item) * self.get_row_count(Table::Warehouse) * n_weeks as i64 + } + + /// Convert config::Table to table::Table for accessing metadata + fn to_meta_table(table: Table) -> MetaTable { + match table { + Table::CallCenter => MetaTable::CallCenter, + Table::CatalogPage => MetaTable::CatalogPage, + Table::CatalogReturns => MetaTable::CatalogReturns, + Table::CatalogSales => MetaTable::CatalogSales, + Table::Customer => MetaTable::Customer, + Table::CustomerAddress => MetaTable::CustomerAddress, + Table::CustomerDemographics => MetaTable::CustomerDemographics, + Table::DateDim => MetaTable::DateDim, + Table::HouseholdDemographics => MetaTable::HouseholdDemographics, + Table::IncomeBand => MetaTable::IncomeBand, + Table::Inventory => MetaTable::Inventory, + Table::Item => MetaTable::Item, + Table::Promotion => MetaTable::Promotion, + Table::Reason => MetaTable::Reason, + Table::ShipMode => MetaTable::ShipMode, + Table::Store => MetaTable::Store, + Table::StoreReturns => MetaTable::StoreReturns, + Table::StoreSales => MetaTable::StoreSales, + Table::TimeDim => MetaTable::TimeDim, + Table::Warehouse => MetaTable::Warehouse, + Table::WebPage => MetaTable::WebPage, + Table::WebReturns => MetaTable::WebReturns, + Table::WebSales => MetaTable::WebSales, + Table::WebSite => MetaTable::WebSite, + Table::DbgenVersion => MetaTable::DbgenVersion, + // Source tables - use a default or panic + _ => panic!( + "Source tables not supported for row count scaling: {:?}", + table + ), + } + } + + /// Get unique ID count for tables that keep history + pub fn get_id_count(&self, table: Table) -> i64 { + let row_count = self.get_row_count(table); + if table.keeps_history() { + let unique_count = (row_count / 6) * 3; + match row_count % 6 { + 1 => unique_count + 1, + 2 | 3 => unique_count + 2, + 4 | 5 => unique_count + 3, + _ => unique_count, + } + } else { + row_count + } + } + + /// Get row count for a specific date for date-based tables. + /// + /// For sales tables (STORE_SALES, CATALOG_SALES, WEB_SALES), this calculates + /// how many rows to generate for a given julian date using the calendar + /// distribution weights. + /// + /// Based on Scaling.getRowCountForDate in Java. + pub fn get_row_count_for_date(&self, table: Table, julian_date: i64) -> i64 { + let row_count = match table { + Table::StoreSales | Table::CatalogSales | Table::WebSales => self.get_row_count(table), + Table::Inventory => { + self.get_row_count(Table::Warehouse) * self.get_id_count(Table::Item) + } + _ => panic!("Invalid table for date scaling: {:?}", table), + }; + + // Convert julian date to a Date + let date = Date::from_julian_days(julian_date as i32); + + // Get the appropriate weights based on year (leap year or not) + let weights = if Date::is_leap_year(date.year()) { + CalendarWeights::SalesLeapYear + } else { + CalendarWeights::Sales + }; + + // Calculate row count for this date using calendar distribution + // The formula: rowCount = (rowCount * dayWeight + calendarTotal/2) / calendarTotal + // This distributes the total row count across dates based on weights + let calendar_total = CalendarDistribution::get_max_weight(weights) as i64 * 5; // 5 years of data + let day_index = CalendarDistribution::get_index_for_date(&date); + let day_weight = CalendarDistribution::get_weight_for_day_number(day_index, weights) as i64; + + let mut result = row_count * day_weight; + result += calendar_total / 2; // rounding + result /= calendar_total; + + result + } + + /// Basic row counts per table at scale factor 1. + #[allow(dead_code)] + fn get_base_row_count(&self, table: Table) -> i64 { + match table { + Table::CallCenter => 6, + Table::CatalogPage => 11718, + Table::CatalogReturns => 160000, // Same as CatalogSales orders (returns are ~10% of sales) + Table::CatalogSales => 160000, // Number of ORDERS, not line items (16 * 10^4) + Table::Customer => 100000, + Table::CustomerAddress => 50000, + Table::CustomerDemographics => 1920800, + Table::DateDim => 73049, + Table::HouseholdDemographics => 7200, + Table::IncomeBand => 20, + Table::Inventory => 11745000, + Table::Item => 18000, + Table::Promotion => 300, + Table::Reason => 35, + Table::ShipMode => 20, + Table::Store => 12, + Table::StoreReturns => 240000, // Same as StoreSales orders (returns are ~10% of sales) + Table::StoreSales => 240000, // Number of ORDERS, not line items (24 * 10^4) + Table::TimeDim => 86400, + Table::Warehouse => 5, + Table::WebPage => 60, + Table::WebReturns => 60000, // Same as WebSales orders (returns are ~10% of sales) + Table::WebSales => 60000, // Number of ORDERS, not line items (60 * 10^3) + Table::WebSite => 30, + Table::DbgenVersion => 1, + Table::SBrand => 1000, + Table::SCustomerAddress => 50000, + Table::SCallCenter => 6, + Table::SCatalog => 100, + Table::SCatalogOrder => 100000, + Table::SCatalogOrderLineitem => 500000, + Table::SCatalogPage => 11718, + Table::SCatalogPromotionalItem => 10000, + Table::SCatalogReturns => 144, + Table::SCategory => 100, + Table::SClass => 100, + Table::SCompany => 100, + Table::SCustomer => 100000, + Table::SInventory => 1000000, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scaling_creation() { + let scaling = Scaling::new(1.0); + assert_eq!(scaling.get_scale(), 1.0); + } + + #[test] + fn test_row_count_calculation() { + let scaling = Scaling::new(2.0); + + // Row count should scale with scale factor (not linear) + let customer_rows = scaling.get_row_count(Table::Customer); + assert_eq!(customer_rows, 144000); + + let store_rows = scaling.get_row_count(Table::Store); + assert_eq!(store_rows, 22); + } + + #[test] + fn test_id_count_for_history_tables() { + let scaling = Scaling::new(1.0); + + // Non-history table: ID count equals row count + let customer_ids = scaling.get_id_count(Table::Customer); + let customer_rows = scaling.get_row_count(Table::Customer); + assert_eq!(customer_ids, customer_rows); + + // History table: ID count is less than row count + let item_ids = scaling.get_id_count(Table::Item); + let item_rows = scaling.get_row_count(Table::Item); + assert!(item_ids <= item_rows); + } + + #[test] + fn test_fractional_scaling() { + let scaling = Scaling::new(0.1); + let customer_rows = scaling.get_row_count(Table::Customer); + assert_eq!(customer_rows, 10000); // 100000 * 0.1 + } + + #[test] + fn test_inventory_scaling() { + // Inventory is computed as: item_id_count × warehouse_count × weeks + // weeks = (JULIAN_DATE_MAXIMUM - JULIAN_DATE_MINIMUM + 7) / 7 + + // Scale 1: 9000 items × 5 warehouses × 261 weeks = 11,745,000 + let scaling_1 = Scaling::new(1.0); + let inventory_rows_1 = scaling_1.get_row_count(Table::Inventory); + assert_eq!(inventory_rows_1, 11_745_000); + + // Scale 10: Should scale with item_id_count and warehouse_count + let scaling_10 = Scaling::new(10.0); + let inventory_rows_10 = scaling_10.get_row_count(Table::Inventory); + + // At scale 10: + // - Items: 102,000 rows → id_count = 51,000 (keeps history) + // - Warehouses: 10 rows + // - Weeks: 261 + // Expected: 51,000 × 10 × 261 = 133,110,000 + assert_eq!(inventory_rows_10, 133_110_000); + } +} diff --git a/tpcdsgen/src/config/session.rs b/tpcdsgen/src/config/session.rs new file mode 100644 index 00000000..d30555fe --- /dev/null +++ b/tpcdsgen/src/config/session.rs @@ -0,0 +1,353 @@ +use crate::config::{Options, Scaling, Table}; + +#[derive(Debug, Clone)] +pub struct Session { + scaling: Scaling, + target_directory: String, + suffix: String, + table: Option
, + null_string: String, + separator: char, + do_not_terminate: bool, + no_sexism: bool, + parallelism: i32, + chunk_number: i32, + overwrite: bool, +} + +impl Default for Session { + fn default() -> Self { + Session::get_default_session() + } +} + +impl Session { + #[allow(clippy::too_many_arguments)] + pub fn new( + scale: f64, + target_directory: String, + suffix: String, + table: Option
, + null_string: String, + separator: char, + do_not_terminate: bool, + no_sexism: bool, + parallelism: i32, + overwrite: bool, + ) -> Self { + Self::new_with_chunk_number( + scale, + target_directory, + suffix, + table, + null_string, + separator, + do_not_terminate, + no_sexism, + parallelism, + 1, // Default chunk number + overwrite, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_with_chunk_number( + scale: f64, + target_directory: String, + suffix: String, + table: Option
, + null_string: String, + separator: char, + do_not_terminate: bool, + no_sexism: bool, + parallelism: i32, + chunk_number: i32, + overwrite: bool, + ) -> Self { + Session { + scaling: Scaling::new(scale), + target_directory, + suffix, + table, + null_string, + separator, + do_not_terminate, + no_sexism, + parallelism, + chunk_number, + overwrite, + } + } + + /// Get default session with all default values + pub fn get_default_session() -> Self { + Options::new().to_session().unwrap() + } + + // Builder-like methods (return new Session with updated field) + pub fn with_table(&self, table: Table) -> Self { + Session { + table: Some(table), + ..self.clone() + } + } + + pub fn with_scale(&self, scale: f64) -> Self { + Session { + scaling: Scaling::new(scale), + ..self.clone() + } + } + + pub fn with_parallelism(&self, parallelism: i32) -> Self { + Session { + parallelism, + ..self.clone() + } + } + + pub fn with_chunk_number(&self, chunk_number: i32) -> Self { + Session { + chunk_number, + ..self.clone() + } + } + + pub fn with_no_sexism(&self, no_sexism: bool) -> Self { + Session { + no_sexism, + ..self.clone() + } + } + + // Accessor methods + pub fn get_scaling(&self) -> &Scaling { + &self.scaling + } + + pub fn get_target_directory(&self) -> &str { + &self.target_directory + } + + pub fn get_suffix(&self) -> &str { + &self.suffix + } + + pub fn generate_only_one_table(&self) -> bool { + self.table.is_some() + } + + pub fn get_only_table_to_generate(&self) -> Table { + self.table + .unwrap_or_else(|| panic!("table not present - call generate_only_one_table() first")) + } + + pub fn get_table(&self) -> Option
{ + self.table + } + + pub fn get_null_string(&self) -> &str { + &self.null_string + } + + pub fn get_separator(&self) -> char { + self.separator + } + + pub fn terminate_rows_with_separator(&self) -> bool { + !self.do_not_terminate + } + + pub fn is_sexist(&self) -> bool { + !self.no_sexism + } + + pub fn get_parallelism(&self) -> i32 { + self.parallelism + } + + pub fn get_chunk_number(&self) -> i32 { + self.chunk_number + } + + pub fn should_overwrite(&self) -> bool { + self.overwrite + } + + /// Reconstruct command line arguments that would produce this session + pub fn get_command_line_arguments(&self) -> String { + let mut output = Vec::new(); + + if self.scaling.get_scale() != Options::DEFAULT_SCALE { + output.push(format!("--scale {}", self.scaling.get_scale())); + } + if self.target_directory != Options::DEFAULT_DIRECTORY { + output.push(format!("--directory {}", self.target_directory)); + } + if self.suffix != Options::DEFAULT_SUFFIX { + output.push(format!("--suffix {}", self.suffix)); + } + if let Some(table) = self.table { + output.push(format!("--table {}", table.get_name())); + } + if self.null_string != Options::DEFAULT_NULL_STRING { + output.push(format!("--null {}", self.null_string)); + } + if self.separator != Options::DEFAULT_SEPARATOR { + output.push(format!("--separator {}", self.separator)); + } + if self.do_not_terminate != Options::DEFAULT_DO_NOT_TERMINATE { + output.push("--do-not-terminate".to_string()); + } + if self.no_sexism != Options::DEFAULT_NO_SEXISM { + output.push("--no-sexism".to_string()); + } + if self.parallelism != Options::DEFAULT_PARALLELISM { + output.push(format!("--parallelism {}", self.parallelism)); + } + if self.overwrite != Options::DEFAULT_OVERWRITE { + output.push("--overwrite".to_string()); + } + + output.join(" ") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_session_creation() { + let session = Session::new( + 1.0, + ".".to_string(), + ".dat".to_string(), + None, + "".to_string(), + '|', + false, + false, + 1, + false, + ); + + assert_eq!(session.get_scaling().get_scale(), 1.0); + assert_eq!(session.get_target_directory(), "."); + assert_eq!(session.get_suffix(), ".dat"); + assert!(!session.generate_only_one_table()); + assert_eq!(session.get_null_string(), ""); + assert_eq!(session.get_separator(), '|'); + assert!(session.terminate_rows_with_separator()); + assert!(session.is_sexist()); + assert_eq!(session.get_parallelism(), 1); + assert_eq!(session.get_chunk_number(), 1); + assert!(!session.should_overwrite()); + } + + #[test] + fn test_default_session() { + let session = Session::get_default_session(); + assert_eq!(session.get_scaling().get_scale(), 1.0); + assert_eq!(session.get_target_directory(), "."); + assert!(!session.generate_only_one_table()); + } + + #[test] + fn test_with_methods() { + let session = Session::get_default_session(); + + let session_with_table = session.with_table(Table::CatalogSales); + assert!(session_with_table.generate_only_one_table()); + assert_eq!( + session_with_table.get_only_table_to_generate(), + Table::CatalogSales + ); + + let session_with_scale = session.with_scale(10.0); + assert_eq!(session_with_scale.get_scaling().get_scale(), 10.0); + + let session_with_parallelism = session.with_parallelism(4); + assert_eq!(session_with_parallelism.get_parallelism(), 4); + + let session_with_chunk = session.with_chunk_number(2); + assert_eq!(session_with_chunk.get_chunk_number(), 2); + + let session_with_no_sexism = session.with_no_sexism(true); + assert!(!session_with_no_sexism.is_sexist()); + } + + #[test] + fn test_generate_only_one_table() { + let session = Session::get_default_session(); + assert!(!session.generate_only_one_table()); + + let session_with_table = session.with_table(Table::StoreSales); + assert!(session_with_table.generate_only_one_table()); + assert_eq!( + session_with_table.get_only_table_to_generate(), + Table::StoreSales + ); + } + + #[test] + #[should_panic(expected = "table not present")] + fn test_get_only_table_when_none() { + let session = Session::get_default_session(); + session.get_only_table_to_generate(); + } + + #[test] + fn test_boolean_accessors() { + let session = Session::new( + 1.0, + ".".to_string(), + ".dat".to_string(), + None, + "".to_string(), + '|', + true, // do_not_terminate = true + true, // no_sexism = true + 1, + false, + ); + + assert!(!session.terminate_rows_with_separator()); // negation of do_not_terminate + assert!(!session.is_sexist()); // negation of no_sexism + } + + #[test] + fn test_command_line_arguments() { + let session = Session::new( + 2.0, + "/tmp".to_string(), + ".csv".to_string(), + Some(Table::CatalogSales), + "NULL".to_string(), + ',', + true, + true, + 4, + true, + ); + + let args = session.get_command_line_arguments(); + assert!(args.contains("--scale 2")); + assert!(args.contains("--directory /tmp")); + assert!(args.contains("--suffix .csv")); + assert!(args.contains("--table catalog_sales")); + assert!(args.contains("--null NULL")); + assert!(args.contains("--separator ,")); + assert!(args.contains("--do-not-terminate")); + assert!(args.contains("--no-sexism")); + assert!(args.contains("--parallelism 4")); + assert!(args.contains("--overwrite")); + } + + #[test] + fn test_command_line_arguments_defaults() { + let session = Session::get_default_session(); + let args = session.get_command_line_arguments(); + assert!(args.is_empty()); // All defaults, so no arguments needed + } +} diff --git a/tpcdsgen/src/config/table.rs b/tpcdsgen/src/config/table.rs new file mode 100644 index 00000000..d424f4a8 --- /dev/null +++ b/tpcdsgen/src/config/table.rs @@ -0,0 +1,283 @@ +use crate::TpcdsError; +use std::fmt; +use std::str::FromStr; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Table { + // Main TPC-DS tables + CallCenter, + CatalogPage, + CatalogReturns, + CatalogSales, + Customer, + CustomerAddress, + CustomerDemographics, + DateDim, + HouseholdDemographics, + IncomeBand, + Inventory, + Item, + Promotion, + Reason, + ShipMode, + Store, + StoreReturns, + StoreSales, + TimeDim, + Warehouse, + WebPage, + WebReturns, + WebSales, + WebSite, + DbgenVersion, + + // Source tables (for updates - simplified for now) + SBrand, + SCustomerAddress, + SCallCenter, + SCatalog, + SCatalogOrder, + SCatalogOrderLineitem, + SCatalogPage, + SCatalogPromotionalItem, + SCatalogReturns, + SCategory, + SClass, + SCompany, + SCustomer, + SInventory, + // TODO(clflushopt): Add remaining tables +} + +impl Table { + /// Get the table name as used in command line + pub fn get_name(&self) -> &'static str { + match self { + Table::CallCenter => "call_center", + Table::CatalogPage => "catalog_page", + Table::CatalogReturns => "catalog_returns", + Table::CatalogSales => "catalog_sales", + Table::Customer => "customer", + Table::CustomerAddress => "customer_address", + Table::CustomerDemographics => "customer_demographics", + Table::DateDim => "date_dim", + Table::HouseholdDemographics => "household_demographics", + Table::IncomeBand => "income_band", + Table::Inventory => "inventory", + Table::Item => "item", + Table::Promotion => "promotion", + Table::Reason => "reason", + Table::ShipMode => "ship_mode", + Table::Store => "store", + Table::StoreReturns => "store_returns", + Table::StoreSales => "store_sales", + Table::TimeDim => "time_dim", + Table::Warehouse => "warehouse", + Table::WebPage => "web_page", + Table::WebReturns => "web_returns", + Table::WebSales => "web_sales", + Table::WebSite => "web_site", + Table::DbgenVersion => "dbgen_version", + Table::SBrand => "s_brand", + Table::SCustomerAddress => "s_customer_address", + Table::SCallCenter => "s_call_center", + Table::SCatalog => "s_catalog", + Table::SCatalogOrder => "s_catalog_order", + Table::SCatalogOrderLineitem => "s_catalog_order_lineitem", + Table::SCatalogPage => "s_catalog_page", + Table::SCatalogPromotionalItem => "s_catalog_promotional_item", + Table::SCatalogReturns => "s_catalog_returns", + Table::SCategory => "s_category", + Table::SClass => "s_class", + Table::SCompany => "s_company", + Table::SCustomer => "s_customer", + Table::SInventory => "s_inventory", + } + } + + /// Get all main tables (non-source tables) + pub fn main_tables() -> Vec
{ + vec![ + Table::CallCenter, + Table::CatalogPage, + Table::CatalogReturns, + Table::CatalogSales, + Table::Customer, + Table::CustomerAddress, + Table::CustomerDemographics, + Table::DateDim, + Table::HouseholdDemographics, + Table::IncomeBand, + Table::Inventory, + Table::Item, + Table::Promotion, + Table::Reason, + Table::ShipMode, + Table::Store, + Table::StoreReturns, + Table::StoreSales, + Table::TimeDim, + Table::Warehouse, + Table::WebPage, + Table::WebReturns, + Table::WebSales, + Table::WebSite, + Table::DbgenVersion, + ] + } + + /// Check if this is a main table (not a source table) + pub fn is_main_table(&self) -> bool { + !matches!( + self, + Table::SBrand + | Table::SCustomerAddress + | Table::SCallCenter + | Table::SCatalog + | Table::SCatalogOrder + | Table::SCatalogOrderLineitem + | Table::SCatalogPage + | Table::SCatalogPromotionalItem + | Table::SCatalogReturns + | Table::SCategory + | Table::SClass + | Table::SCompany + | Table::SCustomer + | Table::SInventory + ) + } + + /// Basic properties for now - will be expanded later + pub fn is_small(&self) -> bool { + // Simplified - these tables have small row counts + matches!( + self, + Table::CallCenter | Table::Store | Table::Warehouse | Table::WebSite + ) + } + + pub fn keeps_history(&self) -> bool { + // Tables that maintain historical data + matches!( + self, + Table::CallCenter | Table::Item | Table::Store | Table::WebPage | Table::WebSite + ) + } + + pub fn is_date_based(&self) -> bool { + // Tables where data generation is based on dates + matches!( + self, + Table::CatalogSales | Table::StoreSales | Table::WebSales | Table::Inventory + ) + } +} + +impl FromStr for Table { + type Err = TpcdsError; + + fn from_str(s: &str) -> Result { + let table_name = s.to_uppercase(); + match table_name.as_str() { + "CALL_CENTER" => Ok(Table::CallCenter), + "CATALOG_PAGE" => Ok(Table::CatalogPage), + "CATALOG_RETURNS" => Ok(Table::CatalogReturns), + "CATALOG_SALES" => Ok(Table::CatalogSales), + "CUSTOMER" => Ok(Table::Customer), + "CUSTOMER_ADDRESS" => Ok(Table::CustomerAddress), + "CUSTOMER_DEMOGRAPHICS" => Ok(Table::CustomerDemographics), + "DATE_DIM" => Ok(Table::DateDim), + "HOUSEHOLD_DEMOGRAPHICS" => Ok(Table::HouseholdDemographics), + "INCOME_BAND" => Ok(Table::IncomeBand), + "INVENTORY" => Ok(Table::Inventory), + "ITEM" => Ok(Table::Item), + "PROMOTION" => Ok(Table::Promotion), + "REASON" => Ok(Table::Reason), + "SHIP_MODE" => Ok(Table::ShipMode), + "STORE" => Ok(Table::Store), + "STORE_RETURNS" => Ok(Table::StoreReturns), + "STORE_SALES" => Ok(Table::StoreSales), + "TIME_DIM" => Ok(Table::TimeDim), + "WAREHOUSE" => Ok(Table::Warehouse), + "WEB_PAGE" => Ok(Table::WebPage), + "WEB_RETURNS" => Ok(Table::WebReturns), + "WEB_SALES" => Ok(Table::WebSales), + "WEB_SITE" => Ok(Table::WebSite), + "DBGEN_VERSION" => Ok(Table::DbgenVersion), + "S_BRAND" => Ok(Table::SBrand), + "S_CUSTOMER_ADDRESS" => Ok(Table::SCustomerAddress), + "S_CALL_CENTER" => Ok(Table::SCallCenter), + "S_CATALOG" => Ok(Table::SCatalog), + "S_CATALOG_ORDER" => Ok(Table::SCatalogOrder), + "S_CATALOG_ORDER_LINEITEM" => Ok(Table::SCatalogOrderLineitem), + "S_CATALOG_PAGE" => Ok(Table::SCatalogPage), + "S_CATALOG_PROMOTIONAL_ITEM" => Ok(Table::SCatalogPromotionalItem), + "S_CATALOG_RETURNS" => Ok(Table::SCatalogReturns), + "S_CATEGORY" => Ok(Table::SCategory), + "S_CLASS" => Ok(Table::SClass), + "S_COMPANY" => Ok(Table::SCompany), + "S_CUSTOMER" => Ok(Table::SCustomer), + "S_INVENTORY" => Ok(Table::SInventory), + _ => Err(TpcdsError::new(&format!("Invalid table name: {}", s))), + } + } +} + +impl fmt::Display for Table { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.get_name()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_table_from_str() { + assert_eq!( + "CATALOG_SALES".parse::
().unwrap(), + Table::CatalogSales + ); + assert_eq!( + "catalog_sales".parse::
().unwrap(), + Table::CatalogSales + ); + assert_eq!("STORE_SALES".parse::
().unwrap(), Table::StoreSales); + + assert!("INVALID_TABLE".parse::
().is_err()); + } + + #[test] + fn test_table_name() { + assert_eq!(Table::CatalogSales.get_name(), "catalog_sales"); + assert_eq!(Table::StoreSales.get_name(), "store_sales"); + } + + #[test] + fn test_main_tables() { + let main_tables = Table::main_tables(); + assert!(main_tables.contains(&Table::CatalogSales)); + assert!(main_tables.contains(&Table::StoreSales)); + assert!(!main_tables.contains(&Table::SBrand)); + } + + #[test] + fn test_table_properties() { + assert!(Table::CallCenter.is_small()); + assert!(Table::CallCenter.keeps_history()); + assert!(!Table::CallCenter.is_date_based()); + + assert!(Table::CatalogSales.is_date_based()); + assert!(!Table::CatalogSales.is_small()); + + assert!(Table::StoreSales.is_main_table()); + assert!(!Table::SBrand.is_main_table()); + } + + #[test] + fn test_display() { + assert_eq!(format!("{}", Table::CatalogSales), "catalog_sales"); + assert_eq!(format!("{}", Table::SBrand), "s_brand"); + } +} diff --git a/tpcdsgen/src/distribution/address_distributions.rs b/tpcdsgen/src/distribution/address_distributions.rs new file mode 100644 index 00000000..9c9607bb --- /dev/null +++ b/tpcdsgen/src/distribution/address_distributions.rs @@ -0,0 +1,76 @@ +use crate::distribution::string_values_distribution::StringValuesDistribution; +use crate::error::Result; +use crate::random::stream::RandomNumberStream; +use std::sync::OnceLock; + +static STREET_NAMES_DISTRIBUTION: OnceLock = OnceLock::new(); +static STREET_TYPES_DISTRIBUTION: OnceLock = OnceLock::new(); +static CITIES_DISTRIBUTION: OnceLock = OnceLock::new(); +static COUNTRIES_DISTRIBUTION: OnceLock = OnceLock::new(); + +#[derive(Debug, Clone, Copy)] +pub enum StreetNamesWeights { + Default = 0, + HalfEmpty = 1, +} + +#[derive(Debug, Clone, Copy)] +pub enum CitiesWeights { + UsgsSkewed = 0, + Uniform = 1, + Large = 2, + Medium = 3, + Small = 4, + UnifiedStepFunction = 5, +} + +pub fn pick_random_street_name( + weights: StreetNamesWeights, + stream: &mut dyn RandomNumberStream, +) -> Result<&'static str> { + let dist = STREET_NAMES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("street_names.dst", 1, 2) + .expect("Failed to load street names distribution") + }); + + dist.pick_random_value(0, weights as usize, stream) +} + +pub fn pick_random_street_type(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = STREET_TYPES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("street_types.dst", 1, 1) + .expect("Failed to load street types distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_city( + weights: CitiesWeights, + stream: &mut dyn RandomNumberStream, +) -> Result<&'static str> { + let dist = CITIES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("cities.dst", 1, 6) + .expect("Failed to load cities distribution") + }); + + dist.pick_random_value(0, weights as usize, stream) +} + +pub fn pick_random_country(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = COUNTRIES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("countries.dst", 1, 1) + .expect("Failed to load countries distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn get_city_at_index(index: usize) -> Result<&'static str> { + let dist = CITIES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("cities.dst", 1, 6) + .expect("Failed to load cities distribution") + }); + + dist.get_value_at_index(0, index) +} diff --git a/tpcdsgen/src/distribution/calendar_distribution.rs b/tpcdsgen/src/distribution/calendar_distribution.rs new file mode 100644 index 00000000..7b993ce7 --- /dev/null +++ b/tpcdsgen/src/distribution/calendar_distribution.rs @@ -0,0 +1,277 @@ +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Weights for calendar distribution (CalendarDistribution.Weights) +#[derive(Debug, Clone, Copy)] +pub enum CalendarWeights { + Uniform = 0, + UniformLeapYear = 1, + Sales = 2, + SalesLeapYear = 3, + Returns = 4, + ReturnsLeapYear = 5, + CombinedSkew = 6, + Low = 7, + Medium = 8, + High = 9, +} + +/// Calendar distribution for date_dim generation (CalendarDistribution) +pub struct CalendarDistribution { + days_of_year: Vec, + quarters: Vec, + holiday_flags: Vec, + weights_lists: Vec>, +} + +impl CalendarDistribution { + const NUM_WEIGHT_FIELDS: usize = 10; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "calendar.dst"; + + /// Days before each month (non-leap and leap year) + pub const DAYS_BEFORE_MONTH: [[i32; 12]; 2] = [ + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], // Non-leap year + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335], // Leap year + ]; + + fn get_instance() -> &'static CalendarDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_calendar_distribution().expect("Failed to load calendar distribution") + }) + } + + fn build_calendar_distribution() -> Result { + let mut days_of_year = Vec::new(); + let mut quarters = Vec::new(); + let mut holiday_flags = Vec::new(); + let mut weights_builders: Vec = (0..Self::NUM_WEIGHT_FIELDS) + .map(|_| WeightsBuilder::new()) + .collect(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (values, weights) in parsed_lines { + if values.len() != 8 { + return Err(TpcdsError::new(&format!( + "Expected line to contain 8 values, but it contained {}: {:?}", + values.len(), + values + ))); + } + + if weights.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weights, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weights.len(), + weights + ))); + } + + // Parse values (only use day_of_year, quarter, and holiday_flag) + // Values are: [0]=day_of_year, [1]=month_name, [2]=day, [3]=season, + // [4]=month_number, [5]=quarter, [6]=first_of_month, [7]=holiday_flag + days_of_year.push(values[0].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse day_of_year '{}': {}", + values[0], e + )) + })?); + + quarters.push(values[5].parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse quarter '{}': {}", values[5], e)) + })?); + + holiday_flags.push(values[7].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse holiday_flag '{}': {}", + values[7], e + )) + })?); + + // Parse weights + for (i, weight_str) in weights.iter().enumerate() { + let weight: i32 = weight_str.parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse weight '{}': {}", weight_str, e)) + })?; + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let weights_lists = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(CalendarDistribution { + days_of_year, + quarters, + holiday_flags, + weights_lists, + }) + } + + /// Get the quarter for a given day index (1-based index) + pub fn get_quarter_at_index(index: i32) -> i32 { + let dist = Self::get_instance(); + dist.quarters[(index - 1) as usize] + } + + /// Get the holiday flag for a given day index (1-based index) + pub fn get_is_holiday_flag_at_index(index: i32) -> i32 { + let dist = Self::get_instance(); + dist.holiday_flags[(index - 1) as usize] + } + + /// Pick a random day of year using weighted distribution (CalendarDistribution.pickRandomDayOfYear) + /// + /// This uses weighted random selection based on the specified weights type. + /// Different weight types model different temporal patterns (sales, returns, uniform, etc.) + /// + /// # Arguments + /// + /// * `weights` - The weight distribution to use for selection + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// A day of year value (1-366) based on the weighted distribution + pub fn pick_random_day_of_year( + weights: CalendarWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result { + let dist = Self::get_instance(); + let weights_list = &dist.weights_lists[weights as usize]; + + let value_ref = pick_random_value(&dist.days_of_year, weights_list, stream)?; + Ok(*value_ref) + } + + /// Get the 0-based index into the calendar distribution for a given date. + /// + /// Based on CalendarDistribution.getIndexForDate in Java. + pub fn get_index_for_date(date: &crate::types::Date) -> i32 { + let leap_year_index = if crate::types::Date::is_leap_year(date.year()) { + 1 + } else { + 0 + }; + Self::DAYS_BEFORE_MONTH[leap_year_index][(date.month() - 1) as usize] + date.day() - 1 + } + + /// Get the weight for a specific day number using the specified weights. + /// + /// Based on CalendarDistribution.getWeightForDayNumber in Java. + /// This reverses the cumulative weights to get the raw (individual day) weight, + /// matching Java's DistributionUtils.getWeightForIndex behavior. + pub fn get_weight_for_day_number(day_number: i32, weights: CalendarWeights) -> i32 { + let dist = Self::get_instance(); + let weights_list = &dist.weights_lists[weights as usize]; + // Reverse the accumulation to get raw weight (like Java's getWeightForIndex) + if day_number == 0 { + weights_list[0] + } else { + weights_list[day_number as usize] - weights_list[(day_number - 1) as usize] + } + } + + /// Get the maximum weight for the specified weights distribution. + /// + /// Based on CalendarDistribution.getMaxWeight in Java. + /// The max weight is the last element in the cumulative weights list. + pub fn get_max_weight(weights: CalendarWeights) -> i32 { + let dist = Self::get_instance(); + let weights_list = &dist.weights_lists[weights as usize]; + *weights_list.last().unwrap_or(&0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calendar_distribution_loading() { + let dist = CalendarDistribution::get_instance(); + assert!(dist.days_of_year.len() > 0); + assert_eq!(dist.quarters.len(), dist.days_of_year.len()); + assert_eq!(dist.holiday_flags.len(), dist.days_of_year.len()); + } + + #[test] + fn test_get_quarter_at_index() { + // Day 1 (Jan 1) should be in Q1 + let quarter = CalendarDistribution::get_quarter_at_index(1); + assert_eq!(quarter, 1); + } + + #[test] + fn test_days_before_month() { + // Non-leap year + assert_eq!(CalendarDistribution::DAYS_BEFORE_MONTH[0][0], 0); // January + assert_eq!(CalendarDistribution::DAYS_BEFORE_MONTH[0][1], 31); // February + + // Leap year + assert_eq!(CalendarDistribution::DAYS_BEFORE_MONTH[1][2], 60); // March in leap year + } + + #[test] + fn test_pick_random_day_of_year() { + use crate::random::RandomNumberStreamImpl; + + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let day = + CalendarDistribution::pick_random_day_of_year(CalendarWeights::Uniform, &mut stream) + .unwrap(); + + // Day should be in valid range [1, 366] + assert!( + day >= 1 && day <= 366, + "Day {} should be in range [1, 366]", + day + ); + } + + #[test] + fn test_pick_random_day_of_year_deterministic() { + use crate::random::RandomNumberStreamImpl; + + // Same seed should produce same day + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let day1 = + CalendarDistribution::pick_random_day_of_year(CalendarWeights::Sales, &mut stream1) + .unwrap(); + let day2 = + CalendarDistribution::pick_random_day_of_year(CalendarWeights::Sales, &mut stream2) + .unwrap(); + + assert_eq!(day1, day2, "Same seed should produce same day"); + } + + #[test] + fn test_pick_random_day_of_year_different_weights() { + use crate::random::RandomNumberStreamImpl; + + // Different weights should potentially produce different results + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + let day_uniform = + CalendarDistribution::pick_random_day_of_year(CalendarWeights::Uniform, &mut stream) + .unwrap(); + let day_sales = + CalendarDistribution::pick_random_day_of_year(CalendarWeights::Sales, &mut stream) + .unwrap(); + + // Both should be valid + assert!(day_uniform >= 1 && day_uniform <= 366); + assert!(day_sales >= 1 && day_sales <= 366); + } +} diff --git a/tpcdsgen/src/distribution/call_center_distributions.rs b/tpcdsgen/src/distribution/call_center_distributions.rs new file mode 100644 index 00000000..ed6bea4f --- /dev/null +++ b/tpcdsgen/src/distribution/call_center_distributions.rs @@ -0,0 +1,123 @@ +use crate::distribution::string_values_distribution::StringValuesDistribution; +use crate::error::Result; +use crate::random::RandomNumberStream; +use std::sync::OnceLock; + +/// Call center distributions (CallCenterDistributions) +pub struct CallCenterDistributions; + +static CALL_CENTERS_DISTRIBUTION: OnceLock = OnceLock::new(); +static CALL_CENTER_CLASSES_DISTRIBUTION: OnceLock = OnceLock::new(); +static CALL_CENTER_HOURS_DISTRIBUTION: OnceLock = OnceLock::new(); + +impl CallCenterDistributions { + /// Initialize all distributions (lazy loading) + fn ensure_initialized() -> Result<()> { + // Initialize call centers distribution + if CALL_CENTERS_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "call_centers.dst", + 1, // 1 value field: name + 2, // 2 weight fields: uniform, sales percentage + )?; + let _ = CALL_CENTERS_DISTRIBUTION.set(dist); + } + + // Initialize call center classes distribution + if CALL_CENTER_CLASSES_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "call_center_classes.dst", + 1, // 1 value field: class + 1, // 1 weight field: frequency + )?; + let _ = CALL_CENTER_CLASSES_DISTRIBUTION.set(dist); + } + + // Initialize call center hours distribution + if CALL_CENTER_HOURS_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "call_center_hours.dst", + 1, // 1 value field: hours + 1, // 1 weight field: frequency + )?; + let _ = CALL_CENTER_HOURS_DISTRIBUTION.set(dist); + } + + Ok(()) + } + + /// Get call center name at specific index + pub fn get_call_center_at_index(index: usize) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = CALL_CENTERS_DISTRIBUTION.get().unwrap(); + dist.get_value_at_index(0, index) + } + + /// Get total number of call centers + pub fn get_number_of_call_centers() -> Result { + Self::ensure_initialized()?; + let dist = CALL_CENTERS_DISTRIBUTION.get().unwrap(); + Ok(dist.get_size()) + } + + /// Pick a random call center class + pub fn pick_random_call_center_class( + stream: &mut dyn RandomNumberStream, + ) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = CALL_CENTER_CLASSES_DISTRIBUTION.get().unwrap(); + dist.pick_random_value(0, 0, stream) + } + + /// Pick random call center hours + pub fn pick_random_call_center_hours( + stream: &mut dyn RandomNumberStream, + ) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = CALL_CENTER_HOURS_DISTRIBUTION.get().unwrap(); + dist.pick_random_value(0, 0, stream) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_call_center_at_index() { + let center = CallCenterDistributions::get_call_center_at_index(0).unwrap(); + assert!(!center.is_empty()); + } + + #[test] + fn test_number_of_call_centers() { + let count = CallCenterDistributions::get_number_of_call_centers().unwrap(); + assert!(count > 0); + } + + #[test] + fn test_pick_random_call_center_class() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let class = CallCenterDistributions::pick_random_call_center_class(&mut stream).unwrap(); + assert!(!class.is_empty()); + } + + #[test] + fn test_pick_random_call_center_hours() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let hours = CallCenterDistributions::pick_random_call_center_hours(&mut stream).unwrap(); + assert!(!hours.is_empty()); + } + + #[test] + fn test_deterministic_selection() { + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let class1 = CallCenterDistributions::pick_random_call_center_class(&mut stream1).unwrap(); + let class2 = CallCenterDistributions::pick_random_call_center_class(&mut stream2).unwrap(); + + assert_eq!(class1, class2); + } +} diff --git a/tpcdsgen/src/distribution/catalog_page_distributions.rs b/tpcdsgen/src/distribution/catalog_page_distributions.rs new file mode 100644 index 00000000..ca67a905 --- /dev/null +++ b/tpcdsgen/src/distribution/catalog_page_distributions.rs @@ -0,0 +1,184 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog page distributions for TPC-DS catalog_page table generation. +//! +//! This module provides distribution of catalog types (monthly, bi-annual, quarterly) +//! with weighted random selection based on distribution frequency and sales volume. + +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Catalog page type distribution (CatalogPageDistributions) +/// +/// Loads catalog_page_types.dst which contains: +/// - 1 value field: catalog type name (monthly, bi-annual, quarterly) +/// - 2 weight fields: distribution frequency, sales volume +/// +/// Only the second weight field (sales volume) is used for random picking. +pub struct CatalogPageTypesDistribution { + values: Vec, // Catalog type names + _weights_list1: Vec, // Distribution frequency weights (not used) + weights_list2: Vec, // Sales volume weights (used for picking) +} + +impl CatalogPageTypesDistribution { + const NUM_VALUE_FIELDS: usize = 1; + const NUM_WEIGHT_FIELDS: usize = 2; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "catalog_page_types.dst"; + + fn get_instance() -> &'static CatalogPageTypesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_catalog_page_types_distribution() + .expect("Failed to load catalog page types distribution") + }) + } + + fn build_catalog_page_types_distribution() -> Result { + let mut values = Vec::new(); + let mut weights_builder1 = WeightsBuilder::new(); + let mut weights_builder2 = WeightsBuilder::new(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (value_fields, weight_fields) in parsed_lines { + if value_fields.len() != Self::NUM_VALUE_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} value field, but it contained {}: {:?}", + Self::NUM_VALUE_FIELDS, + value_fields.len(), + value_fields + ))); + } + + if weight_fields.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weight fields, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weight_fields.len(), + weight_fields + ))); + } + + // Parse value (catalog type name) + values.push(value_fields[0].trim().to_string()); + + // Parse weights + let weight1: i32 = weight_fields[0].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight1 '{}': {}", + weight_fields[0], e + )) + })?; + weights_builder1.compute_and_add_next_weight(weight1)?; + + let weight2: i32 = weight_fields[1].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight2 '{}': {}", + weight_fields[1], e + )) + })?; + weights_builder2.compute_and_add_next_weight(weight2)?; + } + + Ok(CatalogPageTypesDistribution { + values, + _weights_list1: weights_builder1.build(), + weights_list2: weights_builder2.build(), + }) + } + + /// Pick a random catalog page type using sales volume weights. + /// + /// This corresponds to CatalogPageDistributions.pickRandomCatalogPageType() + /// which uses only the second set of weights (sales volume). + /// + /// # Arguments + /// + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// A catalog type string ("monthly", "bi-annual", or "quarterly") + pub fn pick_random_catalog_page_type(stream: &mut dyn RandomNumberStream) -> Result { + let dist = Self::get_instance(); + + // Use the second weight list (sales volume, index 1) + let value_ref = pick_random_value(&dist.values, &dist.weights_list2, stream)?; + Ok(value_ref.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_catalog_page_types_distribution_loading() { + let dist = CatalogPageTypesDistribution::get_instance(); + + // Should have 3 catalog types: monthly, bi-annual, quarterly + assert_eq!(dist.values.len(), 3, "Should have 3 catalog types"); + assert_eq!(dist._weights_list1.len(), 3); + assert_eq!(dist.weights_list2.len(), 3); + } + + #[test] + fn test_pick_random_catalog_page_type() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let catalog_type = + CatalogPageTypesDistribution::pick_random_catalog_page_type(&mut stream).unwrap(); + + // Should be one of the valid types + assert!( + catalog_type == "monthly" || catalog_type == "bi-annual" || catalog_type == "quarterly", + "Catalog type '{}' should be one of: monthly, bi-annual, quarterly", + catalog_type + ); + } + + #[test] + fn test_pick_random_catalog_page_type_deterministic() { + // Same seed should produce same result + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let type1 = + CatalogPageTypesDistribution::pick_random_catalog_page_type(&mut stream1).unwrap(); + let type2 = + CatalogPageTypesDistribution::pick_random_catalog_page_type(&mut stream2).unwrap(); + + assert_eq!(type1, type2, "Same seed should produce same catalog type"); + } + + #[test] + fn test_catalog_type_values() { + let dist = CatalogPageTypesDistribution::get_instance(); + + // Verify expected catalog types are present + let types_set: std::collections::HashSet<&String> = dist.values.iter().collect(); + assert!(types_set.contains(&"monthly".to_string())); + assert!( + types_set.contains(&"bi-annual".to_string()) + || types_set.contains(&"quarterly".to_string()) + ); + } +} diff --git a/tpcdsgen/src/distribution/demographics_distributions.rs b/tpcdsgen/src/distribution/demographics_distributions.rs new file mode 100644 index 00000000..b1964235 --- /dev/null +++ b/tpcdsgen/src/distribution/demographics_distributions.rs @@ -0,0 +1,262 @@ +use crate::distribution::string_values_distribution::StringValuesDistribution as FileBasedStringValuesDistribution; +use crate::distribution::{Distribution, IntValuesDistribution}; +use crate::error::Result; +use std::sync::OnceLock; + +/// Distribution for demographics data including income bands (Demographics) +pub struct DemographicsDistributions; + +impl DemographicsDistributions { + /// Lazy-loaded distribution instance for genders.dst (GENDER_DISTRIBUTION) + fn get_gender_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution("genders.dst", 1, 1) + .expect("Failed to load genders.dst") + }) + } + + /// Lazy-loaded distribution instance for marital_statuses.dst (MARITAL_STATUS_DISTRIBUTION) + fn get_marital_status_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "marital_statuses.dst", + 1, + 1, + ) + .expect("Failed to load marital_statuses.dst") + }) + } + + /// Lazy-loaded distribution instance for education.dst (EDUCATION_DISTRIBUTION) + fn get_education_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "education.dst", + 1, + 4, + ) + .expect("Failed to load education.dst") + }) + } + + /// Lazy-loaded distribution instance for purchase_band.dst (PURCHASE_BAND_DISTRIBUTION) + fn get_purchase_band_distribution() -> &'static IntValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("purchase_band.dst", 1, 1) + .expect("Failed to load purchase_band.dst") + }) + } + + /// Lazy-loaded distribution instance for credit_ratings.dst (CREDIT_RATING_DISTRIBUTION) + fn get_credit_rating_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "credit_ratings.dst", + 1, + 1, + ) + .expect("Failed to load credit_ratings.dst") + }) + } + + /// Lazy-loaded distribution instance for income_band.dst + /// Contains 2 value fields (lower_bound, upper_bound) and 1 weight field + fn get_income_band_distribution() -> &'static IntValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("income_band.dst", 2, 1) + .expect("Failed to load income_band.dst") + }) + } + + /// Lazy-loaded distribution instance for buy_potential.dst (BUY_POTENTIAL_DISTRIBUTION) + fn get_buy_potential_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "buy_potential.dst", + 1, + 1, + ) + .expect("Failed to load buy_potential.dst") + }) + } + + /// Lazy-loaded distribution instance for dep_count.dst (DEP_COUNT_DISTRIBUTION) + fn get_dep_count_distribution() -> &'static IntValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("dep_count.dst", 1, 1) + .expect("Failed to load dep_count.dst") + }) + } + + /// Lazy-loaded distribution instance for vehicle_count.dst (VEHICLE_COUNT_DISTRIBUTION) + fn get_vehicle_count_distribution() -> &'static IntValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("vehicle_count.dst", 1, 1) + .expect("Failed to load vehicle_count.dst") + }) + } + + /// Get gender for index mod size (getGenderForIndexModSize) + pub fn get_gender_for_index_mod_size(index: i64) -> &'static str { + Self::get_gender_distribution() + .get_value_for_index_mod_size(index, 0) + .expect("Failed to get gender value") + } + + /// Get marital status for index mod size (getMaritalStatusForIndexModSize) + pub fn get_marital_status_for_index_mod_size(index: i64) -> &'static str { + Self::get_marital_status_distribution() + .get_value_for_index_mod_size(index, 0) + .expect("Failed to get marital status value") + } + + /// Get education for index mod size (getEducationForIndexModSize) + pub fn get_education_for_index_mod_size(index: i64) -> &'static str { + Self::get_education_distribution() + .get_value_for_index_mod_size(index, 0) + .expect("Failed to get education value") + } + + /// Get purchase band for index mod size (getPurchaseBandForIndexModSize) + pub fn get_purchase_band_for_index_mod_size(index: i64) -> i32 { + Self::get_purchase_band_distribution().get_value_for_index_mod_size(index, 0) + } + + /// Get credit rating for index mod size (getCreditRatingForIndexModSize) + pub fn get_credit_rating_for_index_mod_size(index: i64) -> &'static str { + Self::get_credit_rating_distribution() + .get_value_for_index_mod_size(index, 0) + .expect("Failed to get credit rating value") + } + + /// Get gender distribution size + pub fn get_gender_size() -> usize { + Self::get_gender_distribution().get_size() + } + + /// Get marital status distribution size + pub fn get_marital_status_size() -> usize { + Self::get_marital_status_distribution().get_size() + } + + /// Get education distribution size + pub fn get_education_size() -> usize { + Self::get_education_distribution().get_size() + } + + /// Get purchase band distribution size + pub fn get_purchase_band_size() -> usize { + Self::get_purchase_band_distribution().get_size() + } + + /// Get credit rating distribution size + pub fn get_credit_rating_size() -> usize { + Self::get_credit_rating_distribution().get_size() + } + + /// Get income band lower bound at the specified index (getValueAtIndex) + pub fn get_income_band_lower_bound_at_index(index: usize) -> Result { + Self::get_income_band_distribution().get_value_at_index(0, index) + } + + /// Get income band upper bound at the specified index (getValueAtIndex) + pub fn get_income_band_upper_bound_at_index(index: usize) -> Result { + Self::get_income_band_distribution().get_value_at_index(1, index) + } + + /// Get the size of the income band distribution + pub fn get_income_band_size() -> usize { + Self::get_income_band_distribution().get_value_count(0) + } + + /// Get buy potential for index mod size (getBuyPotentialForIndexModSize) + pub fn get_buy_potential_for_index_mod_size(index: i64) -> &'static str { + Self::get_buy_potential_distribution() + .get_value_for_index_mod_size(index, 0) + .expect("Failed to get buy potential value") + } + + /// Get dep count for index mod size (getDepCountForIndexModSize) + pub fn get_dep_count_for_index_mod_size(index: i64) -> i32 { + Self::get_dep_count_distribution().get_value_for_index_mod_size(index, 0) + } + + /// Get vehicle count for index mod size (getVehicleCountForIndexModSize) + pub fn get_vehicle_count_for_index_mod_size(index: i64) -> i32 { + Self::get_vehicle_count_distribution().get_value_for_index_mod_size(index, 0) + } + + /// Get buy potential distribution size + pub fn get_buy_potential_size() -> usize { + Self::get_buy_potential_distribution().get_size() + } + + /// Get dep count distribution size + pub fn get_dep_count_size() -> usize { + Self::get_dep_count_distribution().get_size() + } + + /// Get vehicle count distribution size + pub fn get_vehicle_count_size() -> usize { + Self::get_vehicle_count_distribution().get_size() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_income_band_distribution() { + // Test that we can load the distribution + let size = DemographicsDistributions::get_income_band_size(); + assert!( + size > 0, + "Income band distribution should have at least one entry" + ); + + // Test that we can get values at valid indices + for i in 0..size.min(5) { + let lower = DemographicsDistributions::get_income_band_lower_bound_at_index(i); + let upper = DemographicsDistributions::get_income_band_upper_bound_at_index(i); + + assert!( + lower.is_ok(), + "Should be able to get lower bound at index {}", + i + ); + assert!( + upper.is_ok(), + "Should be able to get upper bound at index {}", + i + ); + + // Lower bound should be less than or equal to upper bound + let lower_val = lower.unwrap(); + let upper_val = upper.unwrap(); + assert!( + lower_val <= upper_val, + "Lower bound {} should be <= upper bound {} at index {}", + lower_val, + upper_val, + i + ); + } + } + + #[test] + fn test_income_band_out_of_bounds() { + let size = DemographicsDistributions::get_income_band_size(); + let result = DemographicsDistributions::get_income_band_lower_bound_at_index(size + 100); + assert!(result.is_err(), "Should fail for out of bounds index"); + } +} diff --git a/tpcdsgen/src/distribution/embedded_data.rs b/tpcdsgen/src/distribution/embedded_data.rs new file mode 100644 index 00000000..9de05535 --- /dev/null +++ b/tpcdsgen/src/distribution/embedded_data.rs @@ -0,0 +1,88 @@ +//! Embedded distribution data files. +//! +//! This module embeds all .dst files at compile time, eliminating runtime file I/O. +//! Files are stored as raw bytes (ISO-8859-1 encoded). + +/// Get embedded distribution file bytes by filename, returns `None` if the filename +/// is not known. +pub fn get_embedded_distribution(filename: &str) -> Option<&'static [u8]> { + match filename { + "adjectives.dst" => Some(include_bytes!("../../data/adjectives.dst")), + "adverbs.dst" => Some(include_bytes!("../../data/adverbs.dst")), + "articles.dst" => Some(include_bytes!("../../data/articles.dst")), + "auxiliaries.dst" => Some(include_bytes!("../../data/auxiliaries.dst")), + "book_class.dst" => Some(include_bytes!("../../data/book_class.dst")), + "brand_syllables.dst" => Some(include_bytes!("../../data/brand_syllables.dst")), + "buy_potential.dst" => Some(include_bytes!("../../data/buy_potential.dst")), + "calendar.dst" => Some(include_bytes!("../../data/calendar.dst")), + "call_center_classes.dst" => Some(include_bytes!("../../data/call_center_classes.dst")), + "call_center_hours.dst" => Some(include_bytes!("../../data/call_center_hours.dst")), + "call_centers.dst" => Some(include_bytes!("../../data/call_centers.dst")), + "catalog_page_types.dst" => Some(include_bytes!("../../data/catalog_page_types.dst")), + "categories.dst" => Some(include_bytes!("../../data/categories.dst")), + "children_class.dst" => Some(include_bytes!("../../data/children_class.dst")), + "cities.dst" => Some(include_bytes!("../../data/cities.dst")), + "colors.dst" => Some(include_bytes!("../../data/colors.dst")), + "countries.dst" => Some(include_bytes!("../../data/countries.dst")), + "credit_ratings.dst" => Some(include_bytes!("../../data/credit_ratings.dst")), + "dep_count.dst" => Some(include_bytes!("../../data/dep_count.dst")), + "education.dst" => Some(include_bytes!("../../data/education.dst")), + "electronic_class.dst" => Some(include_bytes!("../../data/electronic_class.dst")), + "fips.dst" => Some(include_bytes!("../../data/fips.dst")), + "first_names.dst" => Some(include_bytes!("../../data/first_names.dst")), + "genders.dst" => Some(include_bytes!("../../data/genders.dst")), + "home_class.dst" => Some(include_bytes!("../../data/home_class.dst")), + "hours.dst" => Some(include_bytes!("../../data/hours.dst")), + "income_band.dst" => Some(include_bytes!("../../data/income_band.dst")), + "item_current_price.dst" => Some(include_bytes!("../../data/item_current_price.dst")), + "item_manager_id.dst" => Some(include_bytes!("../../data/item_manager_id.dst")), + "item_manufact_id.dst" => Some(include_bytes!("../../data/item_manufact_id.dst")), + "jewelry_class.dst" => Some(include_bytes!("../../data/jewelry_class.dst")), + "last_names.dst" => Some(include_bytes!("../../data/last_names.dst")), + "location_types.dst" => Some(include_bytes!("../../data/location_types.dst")), + "marital_statuses.dst" => Some(include_bytes!("../../data/marital_statuses.dst")), + "men_class.dst" => Some(include_bytes!("../../data/men_class.dst")), + "music_class.dst" => Some(include_bytes!("../../data/music_class.dst")), + "nouns.dst" => Some(include_bytes!("../../data/nouns.dst")), + "prepositions.dst" => Some(include_bytes!("../../data/prepositions.dst")), + "purchase_band.dst" => Some(include_bytes!("../../data/purchase_band.dst")), + "return_reasons.dst" => Some(include_bytes!("../../data/return_reasons.dst")), + "salutations.dst" => Some(include_bytes!("../../data/salutations.dst")), + "sentences.dst" => Some(include_bytes!("../../data/sentences.dst")), + "ship_mode_carrier.dst" => Some(include_bytes!("../../data/ship_mode_carrier.dst")), + "ship_mode_code.dst" => Some(include_bytes!("../../data/ship_mode_code.dst")), + "ship_mode_type.dst" => Some(include_bytes!("../../data/ship_mode_type.dst")), + "shoe_class.dst" => Some(include_bytes!("../../data/shoe_class.dst")), + "sizes.dst" => Some(include_bytes!("../../data/sizes.dst")), + "sport_class.dst" => Some(include_bytes!("../../data/sport_class.dst")), + "street_names.dst" => Some(include_bytes!("../../data/street_names.dst")), + "street_types.dst" => Some(include_bytes!("../../data/street_types.dst")), + "syllables.dst" => Some(include_bytes!("../../data/syllables.dst")), + "terminators.dst" => Some(include_bytes!("../../data/terminators.dst")), + "top_domains.dst" => Some(include_bytes!("../../data/top_domains.dst")), + "units.dst" => Some(include_bytes!("../../data/units.dst")), + "vehicle_count.dst" => Some(include_bytes!("../../data/vehicle_count.dst")), + "verbs.dst" => Some(include_bytes!("../../data/verbs.dst")), + "web_page_use.dst" => Some(include_bytes!("../../data/web_page_use.dst")), + "women_class.dst" => Some(include_bytes!("../../data/women_class.dst")), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_embedded_files_exist() { + assert!(get_embedded_distribution("call_centers.dst").is_some()); + assert!(get_embedded_distribution("genders.dst").is_some()); + assert!(get_embedded_distribution("nonexistent.dst").is_none()); + } + + #[test] + fn test_embedded_content_not_empty() { + let bytes = get_embedded_distribution("call_centers.dst").unwrap(); + assert!(!bytes.is_empty()); + } +} diff --git a/tpcdsgen/src/distribution/english.rs b/tpcdsgen/src/distribution/english.rs new file mode 100644 index 00000000..a34c5925 --- /dev/null +++ b/tpcdsgen/src/distribution/english.rs @@ -0,0 +1,469 @@ +use crate::distribution::{Distribution, StringValuesDistribution}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use std::sync::OnceLock; + +/// English language distributions for text generation (EnglishDistributions) +pub struct EnglishDistributions; + +impl EnglishDistributions { + /// Get adjectives distribution (lazy initialized) + fn adjectives_distribution() -> &'static StringValuesDistribution { + static ADJECTIVES: OnceLock = OnceLock::new(); + ADJECTIVES.get_or_init(|| { + // Sample adjectives from the Java .dst file with approximate weights + let data = &[ + ("good", 1200), + ("new", 1100), + ("first", 900), + ("last", 800), + ("long", 600), + ("great", 550), + ("little", 500), + ("own", 450), + ("other", 400), + ("old", 380), + ("right", 350), + ("big", 320), + ("high", 300), + ("different", 280), + ("small", 260), + ("large", 240), + ("next", 220), + ("early", 200), + ("young", 180), + ("important", 160), + ("few", 140), + ("public", 120), + ("bad", 100), + ("same", 90), + ("able", 80), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create adjectives distribution") + }) + } + + /// Get adverbs distribution (lazy initialized) + fn adverbs_distribution() -> &'static StringValuesDistribution { + static ADVERBS: OnceLock = OnceLock::new(); + ADVERBS.get_or_init(|| { + // Sample adverbs from the Java .dst file with approximate weights + let data = &[ + ("then", 619), + ("more", 615), + ("also", 592), + ("so", 540), + ("now", 538), + ("only", 524), + ("as", 436), + ("very", 431), + ("just", 426), + ("even", 329), + ("still", 318), + ("too", 316), + ("however", 280), + ("well", 275), + ("here", 270), + ("again", 250), + ("never", 240), + ("always", 230), + ("often", 220), + ("sometimes", 200), + ("rather", 180), + ("quite", 160), + ("almost", 140), + ("perhaps", 120), + ("certainly", 100), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create adverbs distribution") + }) + } + + /// Get articles distribution (lazy initialized) + fn articles_distribution() -> &'static StringValuesDistribution { + static ARTICLES: OnceLock = OnceLock::new(); + ARTICLES.get_or_init(|| { + let data = &[("the", 2000), ("a", 800), ("an", 200)]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create articles distribution") + }) + } + + /// Get auxiliary verbs distribution (lazy initialized) + fn auxiliaries_distribution() -> &'static StringValuesDistribution { + static AUXILIARIES: OnceLock = OnceLock::new(); + AUXILIARIES.get_or_init(|| { + let data = &[ + ("is", 500), + ("was", 400), + ("are", 350), + ("were", 300), + ("be", 250), + ("been", 200), + ("being", 150), + ("have", 400), + ("has", 350), + ("had", 300), + ("will", 250), + ("would", 200), + ("can", 180), + ("could", 160), + ("should", 140), + ("may", 120), + ("might", 100), + ("must", 80), + ("do", 300), + ("does", 250), + ("did", 200), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create auxiliaries distribution") + }) + } + + /// Get nouns distribution (lazy initialized) + fn nouns_distribution() -> &'static StringValuesDistribution { + static NOUNS: OnceLock = OnceLock::new(); + NOUNS.get_or_init(|| { + // Sample nouns with business/commerce focus for TPC-DS + let data = &[ + ("time", 900), + ("person", 800), + ("year", 750), + ("way", 700), + ("day", 650), + ("thing", 600), + ("man", 550), + ("world", 500), + ("life", 450), + ("hand", 400), + ("part", 380), + ("child", 360), + ("eye", 340), + ("woman", 320), + ("place", 300), + ("work", 280), + ("week", 260), + ("case", 240), + ("point", 220), + ("government", 200), + ("company", 190), + ("number", 180), + ("group", 170), + ("problem", 160), + ("fact", 150), + ("business", 140), + ("service", 130), + ("product", 120), + ("customer", 110), + ("order", 100), + ("price", 90), + ("sale", 80), + ("market", 70), + ("store", 60), + ("item", 50), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create nouns distribution") + }) + } + + /// Get prepositions distribution (lazy initialized) + fn prepositions_distribution() -> &'static StringValuesDistribution { + static PREPOSITIONS: OnceLock = OnceLock::new(); + PREPOSITIONS.get_or_init(|| { + let data = &[ + ("of", 1500), + ("to", 1200), + ("in", 1000), + ("for", 800), + ("with", 600), + ("on", 550), + ("by", 500), + ("from", 450), + ("about", 400), + ("at", 380), + ("through", 350), + ("during", 320), + ("before", 300), + ("after", 280), + ("above", 260), + ("below", 240), + ("between", 220), + ("among", 200), + ("against", 180), + ("without", 160), + ("within", 140), + ("throughout", 120), + ("upon", 100), + ("beneath", 80), + ("beside", 60), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create prepositions distribution") + }) + } + + /// Get verbs distribution (lazy initialized) + fn verbs_distribution() -> &'static StringValuesDistribution { + static VERBS: OnceLock = OnceLock::new(); + VERBS.get_or_init(|| { + let data = &[ + ("be", 1000), + ("have", 800), + ("do", 600), + ("say", 500), + ("get", 450), + ("make", 400), + ("go", 380), + ("know", 360), + ("take", 340), + ("see", 320), + ("come", 300), + ("think", 280), + ("look", 260), + ("want", 240), + ("give", 220), + ("use", 200), + ("find", 180), + ("tell", 160), + ("ask", 140), + ("work", 130), + ("seem", 120), + ("feel", 110), + ("try", 100), + ("leave", 90), + ("call", 80), + ("buy", 70), + ("sell", 60), + ("order", 50), + ("ship", 40), + ("return", 30), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create verbs distribution") + }) + } + + /// Get sentence terminators distribution (lazy initialized) + fn terminators_distribution() -> &'static StringValuesDistribution { + static TERMINATORS: OnceLock = OnceLock::new(); + TERMINATORS.get_or_init(|| { + let data = &[(".", 70), ("!", 20), ("?", 10)]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create terminators distribution") + }) + } + + /// Get sentences distribution for complete phrases + fn sentences_distribution() -> &'static StringValuesDistribution { + static SENTENCES: OnceLock = OnceLock::new(); + SENTENCES.get_or_init(|| { + // Pre-built sentences for variety + let data = &[ + ("Great product quality", 100), + ("Excellent customer service", 95), + ("Fast shipping and delivery", 90), + ("Good value for money", 85), + ("Highly recommended item", 80), + ("Perfect for everyday use", 75), + ("Outstanding performance", 70), + ("Superior build quality", 65), + ("Exceptional customer experience", 60), + ("Reliable and durable", 55), + ("Easy to use interface", 50), + ("Professional grade equipment", 45), + ("Innovative design features", 40), + ("Competitive pricing available", 35), + ("Premium quality materials", 30), + ]; + + StringValuesDistribution::from_embedded_data(data) + .expect("Failed to create sentences distribution") + }) + } + + // Public API methods (matching Java interface) + + pub fn pick_random_adjective(stream: &mut dyn RandomNumberStream) -> Result { + Self::adjectives_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_adverb(stream: &mut dyn RandomNumberStream) -> Result { + Self::adverbs_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_article(stream: &mut dyn RandomNumberStream) -> Result { + Self::articles_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_auxiliary(stream: &mut dyn RandomNumberStream) -> Result { + Self::auxiliaries_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_noun(stream: &mut dyn RandomNumberStream) -> Result { + Self::nouns_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_preposition(stream: &mut dyn RandomNumberStream) -> Result { + Self::prepositions_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_verb(stream: &mut dyn RandomNumberStream) -> Result { + Self::verbs_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_terminator(stream: &mut dyn RandomNumberStream) -> Result { + Self::terminators_distribution().pick_random_value(0, 0, stream) + } + + pub fn pick_random_sentence(stream: &mut dyn RandomNumberStream) -> Result { + Self::sentences_distribution().pick_random_value(0, 0, stream) + } + + /// Generate a random phrase by combining words + pub fn generate_random_phrase( + stream: &mut dyn RandomNumberStream, + word_count: usize, + ) -> Result { + if word_count == 0 { + return Ok(String::new()); + } + + let mut words = Vec::new(); + + for i in 0..word_count { + let word = match i % 4 { + 0 => Self::pick_random_article(stream)?, + 1 => Self::pick_random_adjective(stream)?, + 2 => Self::pick_random_noun(stream)?, + 3 => Self::pick_random_verb(stream)?, + _ => Self::pick_random_noun(stream)?, + }; + words.push(word); + } + + Ok(words.join(" ")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_pick_random_adjective() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let adjective = EnglishDistributions::pick_random_adjective(&mut stream).unwrap(); + assert!(!adjective.is_empty()); + println!("Random adjective: {}", adjective); + } + + #[test] + fn test_pick_random_adverb() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let adverb = EnglishDistributions::pick_random_adverb(&mut stream).unwrap(); + assert!(!adverb.is_empty()); + println!("Random adverb: {}", adverb); + } + + #[test] + fn test_pick_random_article() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let article = EnglishDistributions::pick_random_article(&mut stream).unwrap(); + assert!(article == "the" || article == "a" || article == "an"); + println!("Random article: {}", article); + } + + #[test] + fn test_pick_random_noun() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let noun = EnglishDistributions::pick_random_noun(&mut stream).unwrap(); + assert!(!noun.is_empty()); + println!("Random noun: {}", noun); + } + + #[test] + fn test_pick_random_verb() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let verb = EnglishDistributions::pick_random_verb(&mut stream).unwrap(); + assert!(!verb.is_empty()); + println!("Random verb: {}", verb); + } + + #[test] + fn test_pick_random_sentence() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let sentence = EnglishDistributions::pick_random_sentence(&mut stream).unwrap(); + assert!(!sentence.is_empty()); + println!("Random sentence: {}", sentence); + } + + #[test] + fn test_generate_random_phrase() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + let phrase = EnglishDistributions::generate_random_phrase(&mut stream, 4).unwrap(); + assert!(!phrase.is_empty()); + assert!(phrase.contains(' ')); // Should have spaces between words + println!("Random phrase: {}", phrase); + + // Test empty phrase + let empty_phrase = EnglishDistributions::generate_random_phrase(&mut stream, 0).unwrap(); + assert!(empty_phrase.is_empty()); + } + + #[test] + fn test_deterministic_selection() { + // Same seed should produce same results + let mut stream1 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + + let word1 = EnglishDistributions::pick_random_noun(&mut stream1).unwrap(); + let word2 = EnglishDistributions::pick_random_noun(&mut stream2).unwrap(); + + assert_eq!(word1, word2); + } + + #[test] + fn test_all_distributions_work() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test that all distribution methods work without panicking + assert!(EnglishDistributions::pick_random_adjective(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_adverb(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_article(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_auxiliary(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_noun(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_preposition(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_verb(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_terminator(&mut stream).is_ok()); + assert!(EnglishDistributions::pick_random_sentence(&mut stream).is_ok()); + } + + #[test] + fn test_weighted_distribution_variety() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Generate multiple words and ensure we get variety + let mut words = std::collections::HashSet::new(); + for _ in 0..20 { + let word = EnglishDistributions::pick_random_noun(&mut stream).unwrap(); + words.insert(word); + } + + // Should have some variety (not just one word) + assert!(words.len() > 1); + } +} diff --git a/tpcdsgen/src/distribution/english_distributions.rs b/tpcdsgen/src/distribution/english_distributions.rs new file mode 100644 index 00000000..3d41bfef --- /dev/null +++ b/tpcdsgen/src/distribution/english_distributions.rs @@ -0,0 +1,103 @@ +use crate::distribution::string_values_distribution::StringValuesDistribution; +use crate::error::Result; +use crate::random::stream::RandomNumberStream; +use std::sync::OnceLock; + +static ADJECTIVES_DISTRIBUTION: OnceLock = OnceLock::new(); +static ADVERBS_DISTRIBUTION: OnceLock = OnceLock::new(); +static ARTICLES_DISTRIBUTION: OnceLock = OnceLock::new(); +static AUXILIARIES_DISTRIBUTION: OnceLock = OnceLock::new(); +static PREPOSITIONS_DISTRIBUTION: OnceLock = OnceLock::new(); +static NOUNS_DISTRIBUTION: OnceLock = OnceLock::new(); +static SENTENCES_DISTRIBUTION: OnceLock = OnceLock::new(); +static SYLLABLES_DISTRIBUTION: OnceLock = OnceLock::new(); +static TERMINATORS_DISTRIBUTION: OnceLock = OnceLock::new(); +static VERBS_DISTRIBUTION: OnceLock = OnceLock::new(); + +pub fn pick_random_adjective(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = ADJECTIVES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("adjectives.dst", 1, 1) + .expect("Failed to load adjectives distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_adverb(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = ADVERBS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("adverbs.dst", 1, 1) + .expect("Failed to load adverbs distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_article(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = ARTICLES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("articles.dst", 1, 1) + .expect("Failed to load articles distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_auxiliary(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = AUXILIARIES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("auxiliaries.dst", 1, 1) + .expect("Failed to load auxiliaries distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_preposition(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = PREPOSITIONS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("prepositions.dst", 1, 1) + .expect("Failed to load prepositions distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_noun(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = NOUNS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("nouns.dst", 1, 1) + .expect("Failed to load nouns distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_sentence(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = SENTENCES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("sentences.dst", 1, 1) + .expect("Failed to load sentences distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_terminator(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = TERMINATORS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("terminators.dst", 1, 1) + .expect("Failed to load terminators distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn pick_random_verb(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + let dist = VERBS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("verbs.dst", 1, 1) + .expect("Failed to load verbs distribution") + }); + + dist.pick_random_value(0, 0, stream) +} + +pub fn get_syllables_distribution() -> &'static StringValuesDistribution { + SYLLABLES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("syllables.dst", 1, 1) + .expect("Failed to load syllables distribution") + }) +} diff --git a/tpcdsgen/src/distribution/file_loader.rs b/tpcdsgen/src/distribution/file_loader.rs new file mode 100644 index 00000000..38658824 --- /dev/null +++ b/tpcdsgen/src/distribution/file_loader.rs @@ -0,0 +1,138 @@ +use crate::distribution::embedded_data::get_embedded_distribution; +use crate::error::{Result, TpcdsError}; + +/// Loads and parses distribution files (.dst format) +/// Uses compile-time embedded data for zero runtime file I/O. +pub struct DistributionFileLoader; + +impl DistributionFileLoader { + /// Load a distribution file and return parsed lines + /// Each line is split by colon into value and weight parts + /// + /// Distribution files are embedded at compile time, eliminating runtime file dependencies. + pub fn load_distribution_file(filename: &str) -> Result, Vec)>> { + // Get embedded bytes (compile-time embedded) + let bytes = get_embedded_distribution(filename).ok_or_else(|| { + TpcdsError::new(&format!( + "Distribution file not found (not embedded): {}", + filename + )) + })?; + + // Convert ISO-8859-1 to UTF-8 string + let content = bytes.iter().map(|&b| b as char).collect::(); + + let mut parsed_lines = Vec::new(); + + for line in content.lines() { + let trimmed = line.trim(); + + // Skip empty lines and comments + if trimmed.is_empty() || trimmed.starts_with("--") { + continue; + } + + // Split by colon (not escaped colon) + let parts: Vec<&str> = Self::split_by_unescaped_colon(trimmed); + + if parts.len() != 2 { + return Err(TpcdsError::new(&format!( + "Expected line to contain 2 parts but it contains {}: {}", + parts.len(), + trimmed + ))); + } + + let values = if parts[0].is_empty() { + vec![String::new()] // Handle empty string case like ": weight1, weight2" + } else { + Self::parse_comma_separated_values(parts[0])? + }; + let weights = Self::parse_comma_separated_values(parts[1])?; + + parsed_lines.push((values, weights)); + } + + Ok(parsed_lines) + } + + /// Split by colon, but not escaped colon (\\:) + fn split_by_unescaped_colon(line: &str) -> Vec<&str> { + // Simple implementation that splits by colon and trims + // In a full implementation, we'd properly handle escaped colons + line.split(':').map(str::trim).collect() + } + + /// Parse comma-separated values, handling escaped commas (\\,) + fn parse_comma_separated_values(input: &str) -> Result> { + let mut values = Vec::new(); + let mut current = String::new(); + let mut chars = input.trim().chars().peekable(); + + while let Some(ch) = chars.next() { + if ch == '\\' && chars.peek() == Some(&',') { + // Escaped comma, add the comma to current value + current.push(','); + chars.next(); // consume the ',' + } else if ch == '\\' && chars.peek() == Some(&'\\') { + // Escaped backslash + current.push('\\'); + chars.next(); // consume the second '\' + } else if ch == ',' { + // Unescaped comma, split here + values.push(current.trim().to_string()); + current = String::new(); + } else { + current.push(ch); + } + } + + if !current.is_empty() { + values.push(current.trim().to_string()); + } + + // Remove escaping from final values + for value in &mut values { + *value = value.replace("\\\\", "\\"); + } + + Ok(values) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_comma_separated_values() { + let result = DistributionFileLoader::parse_comma_separated_values("a, b, c").unwrap(); + assert_eq!(result, vec!["a", "b", "c"]); + + let result = DistributionFileLoader::parse_comma_separated_values("a\\, b, c").unwrap(); + assert_eq!(result, vec!["a, b", "c"]); + + let result = DistributionFileLoader::parse_comma_separated_values("a\\\\, b").unwrap(); + assert_eq!(result, vec!["a\\", "b"]); + } + + #[test] + fn test_split_by_unescaped_colon() { + let result = DistributionFileLoader::split_by_unescaped_colon("value: 1, 2, 3"); + assert_eq!(result, vec!["value", "1, 2, 3"]); + } + + #[test] + fn test_load_call_centers_distribution() { + // This will test against an actual file + let result = DistributionFileLoader::load_distribution_file("call_centers.dst"); + assert!(result.is_ok()); + + let data = result.unwrap(); + assert!(!data.is_empty()); + + // Check first entry should be something like "New England" + assert_eq!(data[0].0.len(), 1); // 1 value field + assert_eq!(data[0].1.len(), 2); // 2 weight fields + } +} diff --git a/tpcdsgen/src/distribution/fips_county_distribution.rs b/tpcdsgen/src/distribution/fips_county_distribution.rs new file mode 100644 index 00000000..68314038 --- /dev/null +++ b/tpcdsgen/src/distribution/fips_county_distribution.rs @@ -0,0 +1,149 @@ +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_index, WeightsBuilder}; +use crate::error::{Result, TpcdsError}; +use crate::random::RandomNumberStream; +use std::sync::OnceLock; + +static FIPS_COUNTY_DISTRIBUTION: OnceLock = OnceLock::new(); + +#[derive(Debug, Clone)] +pub struct FipsCountyDistribution { + counties: Vec, + state_abbreviations: Vec, + zip_prefixes: Vec, + gmt_offsets: Vec, + weights_lists: Vec>, +} + +#[derive(Debug, Clone, Copy)] +pub enum FipsWeights { + Uniform = 0, + Population = 1, + Timezone = 2, + InZone1 = 3, + InZone2 = 4, + InZone3 = 5, +} + +impl FipsCountyDistribution { + const NUM_WEIGHT_FIELDS: usize = 6; + + fn build_fips_county_distribution() -> Result { + let parsed_lines = DistributionFileLoader::load_distribution_file("fips.dst")?; + + let mut counties = Vec::new(); + let mut state_abbreviations = Vec::new(); + let mut zip_prefixes = Vec::new(); + let mut gmt_offsets = Vec::new(); + let mut weights_builders = vec![WeightsBuilder::new(); Self::NUM_WEIGHT_FIELDS]; + + for (values, weights) in parsed_lines { + if values.len() != 6 { + return Err(TpcdsError::new(&format!( + "Expected line to contain 6 values, but it contained {}: {:?}", + values.len(), + values + ))); + } + + if weights.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weights, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weights.len(), + weights + ))); + } + + // fips codes (values[0]) and state names (values[3]) are never used, so we leave them out + counties.push(values[1].clone()); // County name + state_abbreviations.push(values[2].clone()); // State abbreviation + zip_prefixes.push(values[4].parse::().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse zip prefix '{}': {}", + values[4], e + )) + })?); + gmt_offsets.push(values[5].parse::().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse GMT offset '{}': {}", + values[5], e + )) + })?); + + // Add weights to builders + for (i, weight_str) in weights.iter().enumerate() { + let weight = weight_str.parse::().map_err(|e| { + TpcdsError::new(&format!("Failed to parse weight '{}': {}", weight_str, e)) + })?; + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let weights_lists = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(FipsCountyDistribution { + counties, + state_abbreviations, + zip_prefixes, + gmt_offsets, + weights_lists, + }) + } + + fn get_instance() -> &'static FipsCountyDistribution { + FIPS_COUNTY_DISTRIBUTION.get_or_init(|| { + Self::build_fips_county_distribution() + .expect("Failed to build FIPS county distribution") + }) + } + + pub fn pick_random_index( + weights: FipsWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result { + let instance = Self::get_instance(); + pick_random_index(&instance.weights_lists[weights as usize], stream) + } + + pub fn get_county_at_index(index: usize) -> Result<&'static str> { + let instance = Self::get_instance(); + instance + .counties + .get(index) + .map(|s| s.as_str()) + .ok_or_else(|| TpcdsError::new(&format!("County index {} out of range", index))) + } + + pub fn get_state_abbreviation_at_index(index: usize) -> Result<&'static str> { + let instance = Self::get_instance(); + instance + .state_abbreviations + .get(index) + .map(|s| s.as_str()) + .ok_or_else(|| { + TpcdsError::new(&format!("State abbreviation index {} out of range", index)) + }) + } + + pub fn get_zip_prefix_at_index(index: usize) -> Result { + let instance = Self::get_instance(); + instance + .zip_prefixes + .get(index) + .copied() + .ok_or_else(|| TpcdsError::new(&format!("Zip prefix index {} out of range", index))) + } + + pub fn get_gmt_offset_at_index(index: usize) -> Result { + let instance = Self::get_instance(); + instance + .gmt_offsets + .get(index) + .copied() + .ok_or_else(|| TpcdsError::new(&format!("GMT offset index {} out of range", index))) + } +} diff --git a/tpcdsgen/src/distribution/hours_distribution.rs b/tpcdsgen/src/distribution/hours_distribution.rs new file mode 100644 index 00000000..7c9ebeec --- /dev/null +++ b/tpcdsgen/src/distribution/hours_distribution.rs @@ -0,0 +1,251 @@ +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Weights for hours distribution (HoursDistribution.Weights) +#[derive(Debug, Clone, Copy)] +pub enum HoursWeights { + Uniform = 0, + Store = 1, + CatalogAndWeb = 2, +} + +/// Information about a specific hour (HourInfo) +#[derive(Debug, Clone)] +pub struct HourInfo { + am_pm: String, + shift: String, + sub_shift: String, + meal: String, +} + +impl HourInfo { + pub fn new(am_pm: String, shift: String, sub_shift: String, meal: String) -> Self { + HourInfo { + am_pm, + shift, + sub_shift, + meal, + } + } + + pub fn get_am_pm(&self) -> &str { + &self.am_pm + } + + pub fn get_shift(&self) -> &str { + &self.shift + } + + pub fn get_sub_shift(&self) -> &str { + &self.sub_shift + } + + pub fn get_meal(&self) -> &str { + &self.meal + } +} + +/// Hours distribution for time_dim generation (HoursDistribution) +pub struct HoursDistribution { + hours: Vec, + am_pm: Vec, + shifts: Vec, + sub_shifts: Vec, + meals: Vec, + weights_lists: Vec>, +} + +impl HoursDistribution { + const NUM_WEIGHT_FIELDS: usize = 3; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "hours.dst"; + + fn get_instance() -> &'static HoursDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_hours_distribution().expect("Failed to load hours distribution") + }) + } + + fn build_hours_distribution() -> Result { + let mut hours = Vec::new(); + let mut am_pm = Vec::new(); + let mut shifts = Vec::new(); + let mut sub_shifts = Vec::new(); + let mut meals = Vec::new(); + let mut weights_builders: Vec = (0..Self::NUM_WEIGHT_FIELDS) + .map(|_| WeightsBuilder::new()) + .collect(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (values, weights) in parsed_lines { + if values.len() < 4 || values.len() > 5 { + return Err(TpcdsError::new(&format!( + "Expected line to contain 4 or 5 values, but it contained {}: {:?}", + values.len(), + values + ))); + } + + if weights.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weights, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weights.len(), + weights + ))); + } + + // Parse values: [0]=hour, [1]=am_pm, [2]=shift, [3]=sub_shift, [4]=meal (optional) + hours.push(values[0].parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse hour '{}': {}", values[0], e)) + })?); + + am_pm.push(values[1].trim().to_string()); + shifts.push(values[2].trim().to_string()); + sub_shifts.push(values[3].trim().to_string()); + + // Meal is optional (index 4) + let meal = if values.len() > 4 && !values[4].trim().is_empty() { + values[4].trim().to_string() + } else { + String::new() + }; + meals.push(meal); + + // Parse weights + for (i, weight_str) in weights.iter().enumerate() { + let weight: i32 = weight_str.parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse weight '{}': {}", weight_str, e)) + })?; + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let weights_lists = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(HoursDistribution { + hours, + am_pm, + shifts, + sub_shifts, + meals, + weights_lists, + }) + } + + /// Get hour information for a specific hour (0-23) + pub fn get_hour_info_for_hour(hour: i32) -> HourInfo { + let dist = Self::get_instance(); + HourInfo::new( + dist.am_pm[hour as usize].clone(), + dist.shifts[hour as usize].clone(), + dist.sub_shifts[hour as usize].clone(), + dist.meals[hour as usize].clone(), + ) + } + + /// Pick a random hour using weighted distribution (HoursDistribution.pickRandomHour) + /// + /// This uses weighted random selection based on the specified weights type. + /// Different weight types model different hour patterns for different sales channels. + /// + /// # Arguments + /// + /// * `weights` - The weight distribution to use for selection + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// An hour value (0-23) based on the weighted distribution + pub fn pick_random_hour( + weights: HoursWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result { + let dist = Self::get_instance(); + let weights_list = &dist.weights_lists[weights as usize]; + + let value_ref = pick_random_value(&dist.hours, weights_list, stream)?; + Ok(*value_ref) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hours_distribution_loading() { + let dist = HoursDistribution::get_instance(); + assert_eq!(dist.hours.len(), 24); // Should have 24 hours + assert_eq!(dist.am_pm.len(), 24); + assert_eq!(dist.shifts.len(), 24); + } + + #[test] + fn test_get_hour_info() { + let hour_info = HoursDistribution::get_hour_info_for_hour(0); + assert_eq!(hour_info.get_am_pm(), "AM"); + + let hour_info_12 = HoursDistribution::get_hour_info_for_hour(12); + // Hour 12 should be PM + assert!(hour_info_12.get_am_pm() == "AM" || hour_info_12.get_am_pm() == "PM"); + } + + #[test] + fn test_pick_random_hour() { + use crate::random::RandomNumberStreamImpl; + + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let hour = HoursDistribution::pick_random_hour(HoursWeights::Uniform, &mut stream).unwrap(); + + // Hour should be in valid range [0, 23] + assert!( + hour >= 0 && hour <= 23, + "Hour {} should be in range [0, 23]", + hour + ); + } + + #[test] + fn test_pick_random_hour_deterministic() { + use crate::random::RandomNumberStreamImpl; + + // Same seed should produce same hour + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let hour1 = HoursDistribution::pick_random_hour(HoursWeights::Store, &mut stream1).unwrap(); + let hour2 = HoursDistribution::pick_random_hour(HoursWeights::Store, &mut stream2).unwrap(); + + assert_eq!(hour1, hour2, "Same seed should produce same hour"); + } + + #[test] + fn test_pick_random_hour_different_weights() { + use crate::random::RandomNumberStreamImpl; + + // Different weights should potentially produce different results + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + let hour_uniform = + HoursDistribution::pick_random_hour(HoursWeights::Uniform, &mut stream).unwrap(); + let hour_store = + HoursDistribution::pick_random_hour(HoursWeights::Store, &mut stream).unwrap(); + let hour_catalog = + HoursDistribution::pick_random_hour(HoursWeights::CatalogAndWeb, &mut stream).unwrap(); + + // All should be valid + assert!(hour_uniform >= 0 && hour_uniform <= 23); + assert!(hour_store >= 0 && hour_store <= 23); + assert!(hour_catalog >= 0 && hour_catalog <= 23); + } +} diff --git a/tpcdsgen/src/distribution/int_values.rs b/tpcdsgen/src/distribution/int_values.rs new file mode 100644 index 00000000..ae68d8fc --- /dev/null +++ b/tpcdsgen/src/distribution/int_values.rs @@ -0,0 +1,453 @@ +use crate::distribution::{ + Distribution, DistributionFileLoader, DistributionUtils, WeightsBuilder, +}; +use crate::random::RandomNumberStream; +use crate::{error::Result, TpcdsError}; + +/// Integer-based weighted distribution (IntValuesDistribution) +#[derive(Debug, Clone)] +pub struct IntValuesDistribution { + values_lists: Vec>, + weights_lists: Vec>, +} + +impl IntValuesDistribution { + /// Create new distribution with given values and weights lists + pub fn new(values_lists: Vec>, weights_lists: Vec>) -> Result { + // Validate that values and weights lists have same structure + if values_lists.len() != weights_lists.len() { + return Err(TpcdsError::new( + "Values and weights lists must have same number of lists", + )); + } + + for (i, (values, weights)) in values_lists.iter().zip(weights_lists.iter()).enumerate() { + if values.len() != weights.len() { + return Err(TpcdsError::new(&format!( + "Values list {} and weights list {} must have same length", + i, i + ))); + } + } + + Ok(IntValuesDistribution { + values_lists, + weights_lists, + }) + } + + /// Create distribution from embedded data (for immediate use without files) + pub fn from_embedded_data(data: &[(i32, i32)]) -> Result { + let mut values = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + for (value, weight) in data { + values.push(*value); + weights_builder.compute_and_add_next_weight(*weight)?; + } + + Ok(IntValuesDistribution { + values_lists: vec![values], + weights_lists: vec![weights_builder.build()], + }) + } + + /// Create distribution from DST-style data with multiple weight columns + pub fn from_multi_weight_data(data: &[(i32, &[i32])]) -> Result { + if data.is_empty() { + return Ok(IntValuesDistribution { + values_lists: vec![], + weights_lists: vec![], + }); + } + + let num_weight_columns = data[0].1.len(); + let mut values = Vec::new(); + let mut weights_builders: Vec = (0..num_weight_columns) + .map(|_| WeightsBuilder::new()) + .collect(); + + for (value, weights) in data { + if weights.len() != num_weight_columns { + return Err(TpcdsError::new( + "All data entries must have same number of weights", + )); + } + + values.push(*value); + for (i, &weight) in weights.iter().enumerate() { + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let weights_lists: Vec> = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(IntValuesDistribution { + values_lists: vec![values], + weights_lists, + }) + } + + /// Create uniform distribution (all values have equal weight) + pub fn uniform(values: &[i32]) -> Result { + let data: Vec<(i32, i32)> = values.iter().map(|&v| (v, 1)).collect(); + Self::from_embedded_data(&data) + } + + /// Build an IntValuesDistribution from a distribution file + /// + /// # Arguments + /// * `filename` - The .dst file to load + /// * `num_value_fields` - Number of value fields per line (integer values) + /// * `num_weight_fields` - Number of weight fields per line + pub fn build_int_values_distribution( + filename: &str, + num_value_fields: usize, + num_weight_fields: usize, + ) -> Result { + let parsed_lines = DistributionFileLoader::load_distribution_file(filename)?; + + let mut values_builders: Vec> = vec![Vec::new(); num_value_fields]; + let mut weights_builders: Vec = + vec![WeightsBuilder::new(); num_weight_fields]; + + for (values, weights) in parsed_lines { + if values.len() != num_value_fields { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} values, but it contained {}: {:?}", + num_value_fields, + values.len(), + values + ))); + } + + if weights.len() != num_weight_fields { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weights, but it contained {}: {:?}", + num_weight_fields, + weights.len(), + weights + ))); + } + + // Add values to builders - parse as integers + for (i, value) in values.into_iter().enumerate() { + let int_value: i32 = value.parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse value '{}' as integer: {}", + value, e + )) + })?; + values_builders[i].push(int_value); + } + + // Add weights to builders + for (i, weight_str) in weights.into_iter().enumerate() { + let weight: i32 = weight_str.parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse weight '{}': {}", weight_str, e)) + })?; + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let values_lists = values_builders; + let weights_lists = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(IntValuesDistribution { + values_lists, + weights_lists, + }) + } + + /// Get number of value lists + pub fn get_value_lists_count(&self) -> usize { + self.values_lists.len() + } + + /// Get number of weight lists + pub fn get_weight_lists_count(&self) -> usize { + self.weights_lists.len() + } + + /// Get value count for a specific value list + pub fn get_value_count(&self, value_list: usize) -> usize { + if value_list >= self.values_lists.len() { + return 0; + } + self.values_lists[value_list].len() + } + + /// Get the size of the distribution (number of entries in first value list) + pub fn get_size(&self) -> usize { + if self.values_lists.is_empty() { + 0 + } else { + self.values_lists[0].len() + } + } + + /// Get a value by index modulo the size of the list (getValueForIndexModSize) + pub fn get_value_for_index_mod_size(&self, index: i64, value_list_index: usize) -> i32 { + if value_list_index >= self.values_lists.len() { + panic!("Value list index {} out of range", value_list_index); + } + + let values = &self.values_lists[value_list_index]; + if values.is_empty() { + panic!("Cannot get value from empty distribution"); + } + + let actual_index = (index as usize) % values.len(); + values[actual_index] + } + + /// Pick a random index from the specified weight list + pub fn pick_random_index( + &self, + weight_list_index: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result { + if weight_list_index >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of range, max is {}", + weight_list_index, + self.weights_lists.len().saturating_sub(1) + ))); + } + + DistributionUtils::pick_random_index_from_weights( + &self.weights_lists[weight_list_index], + stream, + ) + } +} + +impl Distribution for IntValuesDistribution { + /// Pick random value based on weights (core method matching Java) + fn pick_random_value( + &self, + value_list: usize, + weight_list: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result { + if value_list >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of bounds", + value_list + ))); + } + if weight_list >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of bounds", + weight_list + ))); + } + + let values = &self.values_lists[value_list]; + let weights = &self.weights_lists[weight_list]; + + if values.len() != weights.len() { + return Err(TpcdsError::new( + "Values and weights lists have different lengths", + )); + } + + if values.is_empty() { + return Err(TpcdsError::new("Cannot pick from empty distribution")); + } + + let index = DistributionUtils::pick_random_index_from_weights(weights, stream)?; + if index >= values.len() { + return Err(TpcdsError::new(&format!( + "Selected index {} out of bounds for values", + index + ))); + } + + Ok(values[index]) + } + + /// Get value at specific index + fn get_value_at_index(&self, value_list: usize, index: usize) -> Result { + if value_list >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of bounds", + value_list + ))); + } + + let values = &self.values_lists[value_list]; + if index >= values.len() { + return Err(TpcdsError::new(&format!( + "Index {} out of bounds for values", + index + ))); + } + + Ok(values[index]) + } + + /// Get number of values in a list + fn get_value_count(&self, value_list: usize) -> usize { + if value_list >= self.values_lists.len() { + 0 + } else { + self.values_lists[value_list].len() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_from_embedded_data() { + let data = &[(1, 30), (2, 20), (3, 50)]; + + let dist = IntValuesDistribution::from_embedded_data(data).unwrap(); + assert_eq!(dist.get_value_lists_count(), 1); + assert_eq!(dist.get_weight_lists_count(), 1); + assert_eq!(dist.get_value_count(0), 3); + + // Test value access + assert_eq!(dist.get_value_at_index(0, 0).unwrap(), 1); + assert_eq!(dist.get_value_at_index(0, 1).unwrap(), 2); + assert_eq!(dist.get_value_at_index(0, 2).unwrap(), 3); + } + + #[test] + fn test_uniform_distribution() { + let values = &[10, 20, 30, 40, 50]; + let dist = IntValuesDistribution::uniform(values).unwrap(); + + assert_eq!(dist.get_value_lists_count(), 1); + assert_eq!(dist.get_value_count(0), 5); + + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test multiple picks - all should be valid values + for _ in 0..10 { + let value = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(values.contains(&value)); + } + } + + #[test] + fn test_from_multi_weight_data() { + let weights1 = [0, 1000]; + let weights2 = [500, 500]; + let weights3 = [1000, 0]; + let data = &[ + (0, weights1.as_slice()), // 0 - never picked from first, always from second + (1, weights2.as_slice()), // 1 - balanced in both + (2, weights3.as_slice()), // 2 - always picked from first, never from second + ]; + + let dist = IntValuesDistribution::from_multi_weight_data(data).unwrap(); + assert_eq!(dist.get_value_lists_count(), 1); + assert_eq!(dist.get_weight_lists_count(), 2); // Two weight columns + assert_eq!(dist.get_value_count(0), 3); + } + + #[test] + fn test_pick_random_value() { + let data = &[ + (100, 1), // Rare value + (200, 99), // Common value + ]; + + let dist = IntValuesDistribution::from_embedded_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test multiple picks - should all be valid + for _ in 0..10 { + let value = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(value == 100 || value == 200); + } + } + + #[test] + fn test_deterministic_behavior() { + let data = &[(1, 25), (2, 25), (3, 25), (4, 25)]; + + let dist = IntValuesDistribution::from_embedded_data(data).unwrap(); + + // Same seed should produce same results + let mut stream1 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + + let value1 = dist.pick_random_value(0, 0, &mut stream1).unwrap(); + let value2 = dist.pick_random_value(0, 0, &mut stream2).unwrap(); + + assert_eq!(value1, value2); + } + + #[test] + fn test_weighted_distribution_bias() { + // Create heavily biased distribution + let data = &[ + (1, 1), // Very rare + (2, 1000), // Very common + ]; + + let dist = IntValuesDistribution::from_embedded_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test many picks - should heavily favor value 2 + let mut count_1 = 0; + let mut count_2 = 0; + + for _ in 0..100 { + match dist.pick_random_value(0, 0, &mut stream).unwrap() { + 1 => count_1 += 1, + 2 => count_2 += 1, + _ => panic!("Unexpected value"), + } + } + + // Should heavily favor 2 (this is probabilistic but very likely) + assert!(count_2 > count_1); + } + + #[test] + fn test_error_conditions() { + let data = &[(42, 100)]; + let dist = IntValuesDistribution::from_embedded_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Invalid list indices + assert!(dist.pick_random_value(1, 0, &mut stream).is_err()); // Invalid value list + assert!(dist.pick_random_value(0, 1, &mut stream).is_err()); // Invalid weight list + assert!(dist.get_value_at_index(1, 0).is_err()); // Invalid value list + assert!(dist.get_value_at_index(0, 1).is_err()); // Invalid index + + // Empty distribution + let empty_dist = IntValuesDistribution::new(vec![], vec![]).unwrap(); + assert!(empty_dist.pick_random_value(0, 0, &mut stream).is_err()); + } + + #[test] + fn test_validation() { + // Mismatched list counts + assert!(IntValuesDistribution::new( + vec![vec![1]], + vec![] // Empty weights but non-empty values + ) + .is_err()); + + // Mismatched value/weight lengths + assert!(IntValuesDistribution::new( + vec![vec![1, 2]], // 2 values + vec![vec![100]] // 1 weight + ) + .is_err()); + } +} diff --git a/tpcdsgen/src/distribution/items_distributions.rs b/tpcdsgen/src/distribution/items_distributions.rs new file mode 100644 index 00000000..089faa37 --- /dev/null +++ b/tpcdsgen/src/distribution/items_distributions.rs @@ -0,0 +1,507 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Item-related distributions for TPC-DS data generation + +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::int_values::IntValuesDistribution; +use crate::distribution::string_values_distribution::StringValuesDistribution; +use crate::distribution::utils::{Distribution, WeightsBuilder}; +use crate::error::{Result, TpcdsError}; +use crate::random::RandomNumberStream; +use crate::types::Decimal; +use std::sync::OnceLock; + +// ============================================================================ +// ItemsDistributions - sizes, colors, units, brand syllables, manager/manufact IDs +// ============================================================================ + +/// Item manager ID distribution +static ITEM_MANAGER_ID_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Item manufact ID distribution +static ITEM_MANUFACT_ID_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Sizes distribution +static SIZES_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Colors distribution +static COLORS_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Units distribution +static UNITS_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Brand syllables distribution +static BRAND_SYLLABLES_DISTRIBUTION: OnceLock = OnceLock::new(); + +/// Weight enums for ID ranges (manager and manufact) +#[derive(Debug, Clone, Copy)] +pub enum IdWeights { + Unified = 0, + Low = 1, + Medium = 2, + High = 3, +} + +/// Weight enums for sizes +#[derive(Debug, Clone, Copy)] +pub enum SizeWeights { + Unified = 0, + NoSize = 1, + Sized = 2, +} + +/// Weight enums for colors +#[derive(Debug, Clone, Copy)] +pub enum ColorsWeights { + Uniform = 0, + Skewed = 1, + LowLikelihood = 2, + MediumLikelihood = 3, + HighLikelihood = 4, +} + +fn get_item_manager_id_distribution() -> &'static IntValuesDistribution { + ITEM_MANAGER_ID_DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("item_manager_id.dst", 3, 4) + .expect("Failed to load item_manager_id.dst") + }) +} + +fn get_item_manufact_id_distribution() -> &'static IntValuesDistribution { + ITEM_MANUFACT_ID_DISTRIBUTION.get_or_init(|| { + IntValuesDistribution::build_int_values_distribution("item_manufact_id.dst", 3, 4) + .expect("Failed to load item_manufact_id.dst") + }) +} + +fn get_sizes_distribution() -> &'static StringValuesDistribution { + SIZES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("sizes.dst", 1, 3) + .expect("Failed to load sizes.dst") + }) +} + +fn get_colors_distribution() -> &'static StringValuesDistribution { + COLORS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("colors.dst", 1, 5) + .expect("Failed to load colors.dst") + }) +} + +fn get_units_distribution() -> &'static StringValuesDistribution { + UNITS_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("units.dst", 1, 1) + .expect("Failed to load units.dst") + }) +} + +/// Get brand syllables distribution for word generation +pub fn get_brand_syllables_distribution() -> &'static StringValuesDistribution { + BRAND_SYLLABLES_DISTRIBUTION.get_or_init(|| { + StringValuesDistribution::build_string_values_distribution("brand_syllables.dst", 1, 1) + .expect("Failed to load brand_syllables.dst") + }) +} + +/// Pick random manager ID range +pub fn pick_random_manager_id_range( + id_weights: IdWeights, + stream: &mut dyn RandomNumberStream, +) -> Result<(i32, i32)> { + let dist = get_item_manager_id_distribution(); + let index = dist.pick_random_index(id_weights as usize, stream)?; + let min = dist.get_value_at_index(1, index)?; + let max = dist.get_value_at_index(2, index)?; + Ok((min, max)) +} + +/// Pick random manufact ID range +pub fn pick_random_manufact_id_range( + id_weights: IdWeights, + stream: &mut dyn RandomNumberStream, +) -> Result<(i32, i32)> { + let dist = get_item_manufact_id_distribution(); + let index = dist.pick_random_index(id_weights as usize, stream)?; + let min = dist.get_value_at_index(1, index)?; + let max = dist.get_value_at_index(2, index)?; + Ok((min, max)) +} + +/// Pick random size +pub fn pick_random_size( + size_weights: SizeWeights, + stream: &mut dyn RandomNumberStream, +) -> Result { + let dist = get_sizes_distribution(); + Ok(dist + .pick_random_value(0, size_weights as usize, stream)? + .to_string()) +} + +/// Pick random color +pub fn pick_random_color( + colors_weights: ColorsWeights, + stream: &mut dyn RandomNumberStream, +) -> Result { + let dist = get_colors_distribution(); + Ok(dist + .pick_random_value(0, colors_weights as usize, stream)? + .to_string()) +} + +/// Pick random unit +pub fn pick_random_unit(stream: &mut dyn RandomNumberStream) -> Result { + let dist = get_units_distribution(); + Ok(dist.pick_random_value(0, 0, stream)?.to_string()) +} + +// ============================================================================ +// CategoriesDistribution - categories with hasSize flag +// ============================================================================ + +struct CategoriesDistributionData { + names: Vec, + has_sizes: Vec, + weights: Vec, +} + +static CATEGORIES_DISTRIBUTION: OnceLock = OnceLock::new(); + +fn get_categories_distribution() -> &'static CategoriesDistributionData { + CATEGORIES_DISTRIBUTION.get_or_init(|| { + let parsed_lines = DistributionFileLoader::load_distribution_file("categories.dst") + .expect("Failed to load categories.dst"); + + let mut names = Vec::new(); + let mut has_sizes = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + for (values, weights) in parsed_lines { + if values.len() != 3 { + panic!( + "Expected line to contain 3 values, but it contained {}: {:?}", + values.len(), + values + ); + } + if weights.len() != 1 { + panic!( + "Expected line to contain 1 weight, but it contained {}: {:?}", + weights.len(), + weights + ); + } + + names.push(values[0].clone()); + // values[1] is the class distribution name (unused) + has_sizes.push(values[2].parse().expect("Failed to parse hasSize")); + + let weight: i32 = weights[0].parse().expect("Failed to parse weight"); + weights_builder + .compute_and_add_next_weight(weight) + .expect("Failed to compute weight"); + } + + CategoriesDistributionData { + names, + has_sizes, + weights: weights_builder.build(), + } + }) +} + +/// Pick random category index +pub fn pick_random_category_index(stream: &mut dyn RandomNumberStream) -> Result { + let dist = get_categories_distribution(); + crate::distribution::utils::pick_random_index(&dist.weights, stream) +} + +/// Get category name at index +pub fn get_category_at_index(index: usize) -> &'static str { + let dist = get_categories_distribution(); + &dist.names[index] +} + +/// Get hasSize flag at index +pub fn get_has_size_at_index(index: usize) -> i32 { + let dist = get_categories_distribution(); + dist.has_sizes[index] +} + +// ============================================================================ +// CategoryClassDistributions - class distributions per category +// ============================================================================ + +#[derive(Debug, Clone)] +pub struct CategoryClass { + id: i64, + name: String, + brand_count: i32, +} + +impl CategoryClass { + pub fn get_id(&self) -> i64 { + self.id + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn get_brand_count(&self) -> i32 { + self.brand_count + } +} + +struct CategoryClassDistribution { + names: Vec, + brand_counts: Vec, + weights: Vec, +} + +impl CategoryClassDistribution { + fn build(filename: &str) -> Self { + let parsed_lines = DistributionFileLoader::load_distribution_file(filename) + .unwrap_or_else(|e| panic!("Failed to load {}: {}", filename, e)); + + let mut names = Vec::new(); + let mut brand_counts = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + for (values, weights) in parsed_lines { + if values.len() != 2 { + panic!( + "Expected line to contain 2 values, but it contained {}: {:?}", + values.len(), + values + ); + } + if weights.len() != 1 { + panic!( + "Expected line to contain 1 weight, but it contained {}: {:?}", + weights.len(), + weights + ); + } + + names.push(values[0].clone()); + brand_counts.push(values[1].parse().expect("Failed to parse brand_count")); + + let weight: i32 = weights[0].parse().expect("Failed to parse weight"); + weights_builder + .compute_and_add_next_weight(weight) + .expect("Failed to compute weight"); + } + + CategoryClassDistribution { + names, + brand_counts, + weights: weights_builder.build(), + } + } + + fn pick_random_category_class( + &self, + stream: &mut dyn RandomNumberStream, + ) -> Result { + let index = crate::distribution::utils::pick_random_index(&self.weights, stream)?; + Ok(CategoryClass { + id: (index + 1) as i64, + name: self.names[index].clone(), + brand_count: self.brand_counts[index], + }) + } +} + +static CATEGORY_CLASS_DISTRIBUTIONS: OnceLock> = OnceLock::new(); + +fn get_category_class_distributions() -> &'static Vec { + CATEGORY_CLASS_DISTRIBUTIONS.get_or_init(|| { + vec![ + CategoryClassDistribution::build("women_class.dst"), + CategoryClassDistribution::build("men_class.dst"), + CategoryClassDistribution::build("children_class.dst"), + CategoryClassDistribution::build("shoe_class.dst"), + CategoryClassDistribution::build("music_class.dst"), + CategoryClassDistribution::build("jewelry_class.dst"), + CategoryClassDistribution::build("home_class.dst"), + CategoryClassDistribution::build("sport_class.dst"), + CategoryClassDistribution::build("book_class.dst"), + CategoryClassDistribution::build("electronic_class.dst"), + ] + }) +} + +/// Pick random category class for a given category ID +pub fn pick_random_category_class( + category_id: usize, + stream: &mut dyn RandomNumberStream, +) -> Result { + let distributions = get_category_class_distributions(); + if category_id >= distributions.len() { + return Err(TpcdsError::new(&format!( + "categoryId {} is not less than {}", + category_id, + distributions.len() + ))); + } + distributions[category_id].pick_random_category_class(stream) +} + +// ============================================================================ +// ItemCurrentPriceDistribution - price ranges +// ============================================================================ + +struct ItemCurrentPriceDistributionData { + mins: Vec, + maxes: Vec, + weights_lists: Vec>, +} + +static ITEM_CURRENT_PRICE_DISTRIBUTION: OnceLock = + OnceLock::new(); + +fn get_item_current_price_distribution() -> &'static ItemCurrentPriceDistributionData { + ITEM_CURRENT_PRICE_DISTRIBUTION.get_or_init(|| { + let parsed_lines = DistributionFileLoader::load_distribution_file("item_current_price.dst") + .expect("Failed to load item_current_price.dst"); + + let num_weight_fields = 4; + let mut mins = Vec::new(); + let mut maxes = Vec::new(); + let mut weights_builders: Vec = (0..num_weight_fields) + .map(|_| WeightsBuilder::new()) + .collect(); + + for (values, weights) in parsed_lines { + if values.len() != 3 { + panic!( + "Expected line to contain 3 values, but it contained {}: {:?}", + values.len(), + values + ); + } + if weights.len() != num_weight_fields { + panic!( + "Expected line to contain {} weights, but it contained {}: {:?}", + num_weight_fields, + weights.len(), + weights + ); + } + + // values[0] is index (unused) + mins.push(Decimal::parse_decimal(&values[1]).expect("Failed to parse min decimal")); + maxes.push(Decimal::parse_decimal(&values[2]).expect("Failed to parse max decimal")); + + for (i, weight_str) in weights.iter().enumerate() { + let weight: i32 = weight_str.parse().expect("Failed to parse weight"); + weights_builders[i] + .compute_and_add_next_weight(weight) + .expect("Failed to compute weight"); + } + } + + ItemCurrentPriceDistributionData { + mins, + maxes, + weights_lists: weights_builders.into_iter().map(|b| b.build()).collect(), + } + }) +} + +/// Pick random current price range (returns min and max) +pub fn pick_random_current_price_range( + stream: &mut dyn RandomNumberStream, +) -> Result<(Decimal, Decimal)> { + let dist = get_item_current_price_distribution(); + let index = crate::distribution::utils::pick_random_index(&dist.weights_lists[0], stream)?; + Ok((dist.mins[index], dist.maxes[index])) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_pick_random_size() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let size = pick_random_size(SizeWeights::Sized, &mut stream).unwrap(); + assert!(!size.is_empty()); + } + + #[test] + fn test_pick_random_color() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let color = pick_random_color(ColorsWeights::Skewed, &mut stream).unwrap(); + assert!(!color.is_empty()); + } + + #[test] + fn test_pick_random_unit() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let unit = pick_random_unit(&mut stream).unwrap(); + assert!(!unit.is_empty()); + } + + #[test] + fn test_pick_random_category_index() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let index = pick_random_category_index(&mut stream).unwrap(); + assert!(index < 10); // Should be less than number of categories + } + + #[test] + fn test_get_category_at_index() { + let category = get_category_at_index(0); + assert!(!category.is_empty()); + } + + #[test] + fn test_pick_random_category_class() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let class = pick_random_category_class(0, &mut stream).unwrap(); + assert!(!class.get_name().is_empty()); + assert!(class.get_brand_count() > 0); + } + + #[test] + fn test_pick_random_current_price_range() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let (min, max) = pick_random_current_price_range(&mut stream).unwrap(); + assert!(min.get_number() <= max.get_number()); + } + + #[test] + fn test_pick_random_manager_id_range() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let (min, max) = pick_random_manager_id_range(IdWeights::Unified, &mut stream).unwrap(); + assert!(min <= max); + } + + #[test] + fn test_pick_random_manufact_id_range() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let (min, max) = pick_random_manufact_id_range(IdWeights::Unified, &mut stream).unwrap(); + assert!(min <= max); + } + + #[test] + fn test_brand_syllables_distribution() { + let dist = get_brand_syllables_distribution(); + assert!(dist.get_size() > 0); + } +} diff --git a/tpcdsgen/src/distribution/location_types_distribution.rs b/tpcdsgen/src/distribution/location_types_distribution.rs new file mode 100644 index 00000000..ffd10c20 --- /dev/null +++ b/tpcdsgen/src/distribution/location_types_distribution.rs @@ -0,0 +1,232 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Location types distribution for customer_address table generation. +//! +//! This module provides distribution of location types (single family, condo, apartment) +//! with weighted random selection. + +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Weights for location types distribution (LocationTypesDistribution.LocationTypeWeights) +#[derive(Debug, Clone, Copy)] +pub enum LocationTypeWeights { + Uniform = 0, + DistributionFrequency = 1, +} + +/// Location types distribution (LocationTypesDistribution) +/// +/// Loads location_types.dst which contains: +/// - 1 value field: location type name (single family, condo, apartment) +/// - 2 weight fields: uniform, distribution frequency +pub struct LocationTypesDistribution { + values: Vec, // Location type names + weights_list1: Vec, // Uniform weights + weights_list2: Vec, // Distribution frequency weights +} + +impl LocationTypesDistribution { + const NUM_VALUE_FIELDS: usize = 1; + const NUM_WEIGHT_FIELDS: usize = 2; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "location_types.dst"; + + fn get_instance() -> &'static LocationTypesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_location_types_distribution() + .expect("Failed to load location types distribution") + }) + } + + fn build_location_types_distribution() -> Result { + let mut values = Vec::new(); + let mut weights_builder1 = WeightsBuilder::new(); + let mut weights_builder2 = WeightsBuilder::new(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (value_fields, weight_fields) in parsed_lines { + if value_fields.len() != Self::NUM_VALUE_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} value field, but it contained {}: {:?}", + Self::NUM_VALUE_FIELDS, + value_fields.len(), + value_fields + ))); + } + + if weight_fields.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weight fields, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weight_fields.len(), + weight_fields + ))); + } + + // Parse value (location type name) + values.push(value_fields[0].trim().to_string()); + + // Parse weights + let weight1: i32 = weight_fields[0].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight1 '{}': {}", + weight_fields[0], e + )) + })?; + weights_builder1.compute_and_add_next_weight(weight1)?; + + let weight2: i32 = weight_fields[1].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight2 '{}': {}", + weight_fields[1], e + )) + })?; + weights_builder2.compute_and_add_next_weight(weight2)?; + } + + Ok(LocationTypesDistribution { + values, + weights_list1: weights_builder1.build(), + weights_list2: weights_builder2.build(), + }) + } + + /// Pick a random location type using specified weights. + /// + /// This corresponds to LocationTypesDistribution.pickRandomLocationType() + /// + /// # Arguments + /// + /// * `weights` - Which weight distribution to use (Uniform or DistributionFrequency) + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// A location type string ("single family", "condo", or "apartment") + pub fn pick_random_location_type( + weights: LocationTypeWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result { + let dist = Self::get_instance(); + + let weights_list = match weights { + LocationTypeWeights::Uniform => &dist.weights_list1, + LocationTypeWeights::DistributionFrequency => &dist.weights_list2, + }; + + let value_ref = pick_random_value(&dist.values, weights_list, stream)?; + Ok(value_ref.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_location_types_distribution_loading() { + let dist = LocationTypesDistribution::get_instance(); + + // Should have 3 location types: single family, condo, apartment + assert_eq!(dist.values.len(), 3, "Should have 3 location types"); + assert_eq!(dist.weights_list1.len(), 3); + assert_eq!(dist.weights_list2.len(), 3); + } + + #[test] + fn test_pick_random_location_type() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let location_type = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::DistributionFrequency, + &mut stream, + ) + .unwrap(); + + // Should be one of the valid types + assert!( + location_type == "single family" + || location_type == "condo" + || location_type == "apartment", + "Location type '{}' should be one of: single family, condo, apartment", + location_type + ); + } + + #[test] + fn test_pick_random_location_type_deterministic() { + // Same seed should produce same result + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let type1 = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::Uniform, + &mut stream1, + ) + .unwrap(); + let type2 = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::Uniform, + &mut stream2, + ) + .unwrap(); + + assert_eq!(type1, type2, "Same seed should produce same location type"); + } + + #[test] + fn test_location_type_values() { + let dist = LocationTypesDistribution::get_instance(); + + // Verify expected location types are present + let types_set: std::collections::HashSet<&String> = dist.values.iter().collect(); + assert!(types_set.contains(&"single family".to_string())); + assert!( + types_set.contains(&"condo".to_string()) + || types_set.contains(&"apartment".to_string()) + ); + } + + #[test] + fn test_both_weight_types() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Both weight types should work + let type_uniform = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::Uniform, + &mut stream, + ) + .unwrap(); + let type_dist = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::DistributionFrequency, + &mut stream, + ) + .unwrap(); + + // Both should be valid + assert!( + type_uniform == "single family" + || type_uniform == "condo" + || type_uniform == "apartment" + ); + assert!(type_dist == "single family" || type_dist == "condo" || type_dist == "apartment"); + } +} diff --git a/tpcdsgen/src/distribution/mod.rs b/tpcdsgen/src/distribution/mod.rs new file mode 100644 index 00000000..00abfa9c --- /dev/null +++ b/tpcdsgen/src/distribution/mod.rs @@ -0,0 +1,44 @@ +pub mod address_distributions; +pub mod calendar_distribution; +pub mod call_center_distributions; +pub mod catalog_page_distributions; +pub mod demographics_distributions; +pub mod embedded_data; +pub mod english; +pub mod english_distributions; +pub mod file_loader; +pub mod fips_county_distribution; +pub mod hours_distribution; +pub mod int_values; +pub mod items_distributions; +pub mod location_types_distribution; +pub mod names_distributions; +pub mod return_reasons_distribution; +pub mod ship_mode_distributions; +pub mod string_values; +pub mod string_values_distribution; +pub mod top_domains_distribution; +pub mod utils; +pub mod web_page_use_distribution; + +pub use address_distributions::*; +pub use calendar_distribution::CalendarDistribution; +pub use call_center_distributions::CallCenterDistributions; +pub use catalog_page_distributions::CatalogPageTypesDistribution; +pub use demographics_distributions::DemographicsDistributions; +pub use english::EnglishDistributions; +pub use english_distributions::*; +pub use file_loader::DistributionFileLoader; +pub use fips_county_distribution::{FipsCountyDistribution, FipsWeights}; +pub use hours_distribution::{HourInfo, HoursDistribution}; +pub use int_values::IntValuesDistribution; +pub use items_distributions::*; +pub use location_types_distribution::{LocationTypeWeights, LocationTypesDistribution}; +pub use names_distributions::{FirstNamesWeights, NamesDistributions, SalutationsWeights}; +pub use return_reasons_distribution::ReturnReasonsDistribution; +pub use ship_mode_distributions::ShipModeDistributions; +pub use string_values::StringValuesDistribution; +pub use string_values_distribution::StringValuesDistribution as FileBasedStringValuesDistribution; +pub use top_domains_distribution::TopDomainsDistribution; +pub use utils::{Distribution, DistributionUtils, WeightsBuilder}; +pub use web_page_use_distribution::WebPageUseDistribution; diff --git a/tpcdsgen/src/distribution/names_distributions.rs b/tpcdsgen/src/distribution/names_distributions.rs new file mode 100644 index 00000000..fef14ac5 --- /dev/null +++ b/tpcdsgen/src/distribution/names_distributions.rs @@ -0,0 +1,209 @@ +use crate::distribution::string_values_distribution::StringValuesDistribution; +use crate::error::Result; +use crate::random::RandomNumberStream; +use std::sync::OnceLock; + +/// First names weight categories (FirstNamesWeights enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FirstNamesWeights { + MaleFrequency = 0, + FemaleFrequency = 1, + GeneralFrequency = 2, +} + +/// Salutations weight categories (SalutationsWeights enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SalutationsWeights { + GenderNeutral = 0, + Male = 1, + Female = 2, +} + +/// Names distributions (NamesDistributions) +pub struct NamesDistributions; + +static FIRST_NAMES_DISTRIBUTION: OnceLock = OnceLock::new(); +static LAST_NAMES_DISTRIBUTION: OnceLock = OnceLock::new(); +static SALUTATIONS_DISTRIBUTION: OnceLock = OnceLock::new(); + +impl NamesDistributions { + /// Initialize all distributions (lazy loading) + fn ensure_initialized() -> Result<()> { + // Initialize first names distribution + if FIRST_NAMES_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "first_names.dst", + 1, // 1 value field: name + 3, // 3 weight fields: male freq, female freq, general freq + )?; + let _ = FIRST_NAMES_DISTRIBUTION.set(dist); + } + + // Initialize last names distribution + if LAST_NAMES_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "last_names.dst", + 1, // 1 value field: name + 1, // 1 weight field: frequency + )?; + let _ = LAST_NAMES_DISTRIBUTION.set(dist); + } + + // Initialize salutations distribution + if SALUTATIONS_DISTRIBUTION.get().is_none() { + let dist = StringValuesDistribution::build_string_values_distribution( + "salutations.dst", + 1, // 1 value field: salutation + 3, // 3 weight fields: gender neutral, male, female + )?; + let _ = SALUTATIONS_DISTRIBUTION.set(dist); + } + + Ok(()) + } + + /// Pick a random first name using the specified weight category + pub fn pick_random_first_name( + weights: FirstNamesWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = FIRST_NAMES_DISTRIBUTION.get().unwrap(); + dist.pick_random_value(0, weights as usize, stream) + } + + /// Pick a random index from first names using the specified weight category + pub fn pick_random_index( + weights: FirstNamesWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result { + Self::ensure_initialized()?; + let dist = FIRST_NAMES_DISTRIBUTION.get().unwrap(); + dist.pick_random_index(weights as usize, stream) + } + + /// Get first name from specific index + pub fn get_first_name_from_index(index: usize) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = FIRST_NAMES_DISTRIBUTION.get().unwrap(); + dist.get_value_at_index(0, index) + } + + /// Get weight for specific index and weight category + pub fn get_weight_for_index(index: usize, weights: FirstNamesWeights) -> Result { + Self::ensure_initialized()?; + let dist = FIRST_NAMES_DISTRIBUTION.get().unwrap(); + dist.get_weight_for_index(index, weights as usize) + } + + /// Pick a random last name + pub fn pick_random_last_name(stream: &mut dyn RandomNumberStream) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = LAST_NAMES_DISTRIBUTION.get().unwrap(); + dist.pick_random_value(0, 0, stream) + } + + /// Pick a random salutation using the specified weight category + pub fn pick_random_salutation( + weights: SalutationsWeights, + stream: &mut dyn RandomNumberStream, + ) -> Result<&'static str> { + Self::ensure_initialized()?; + let dist = SALUTATIONS_DISTRIBUTION.get().unwrap(); + dist.pick_random_value(0, weights as usize, stream) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_pick_random_first_name_male() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let name = NamesDistributions::pick_random_first_name( + FirstNamesWeights::MaleFrequency, + &mut stream, + ) + .unwrap(); + assert!(!name.is_empty()); + } + + #[test] + fn test_pick_random_first_name_female() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let name = NamesDistributions::pick_random_first_name( + FirstNamesWeights::FemaleFrequency, + &mut stream, + ) + .unwrap(); + assert!(!name.is_empty()); + } + + #[test] + fn test_pick_random_first_name_general() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let name = NamesDistributions::pick_random_first_name( + FirstNamesWeights::GeneralFrequency, + &mut stream, + ) + .unwrap(); + assert!(!name.is_empty()); + } + + #[test] + fn test_pick_random_last_name() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let name = NamesDistributions::pick_random_last_name(&mut stream).unwrap(); + assert!(!name.is_empty()); + } + + #[test] + fn test_pick_random_salutation() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + let neutral = NamesDistributions::pick_random_salutation( + SalutationsWeights::GenderNeutral, + &mut stream, + ) + .unwrap(); + assert!(!neutral.is_empty()); + + let male = + NamesDistributions::pick_random_salutation(SalutationsWeights::Male, &mut stream) + .unwrap(); + assert!(!male.is_empty()); + + let female = + NamesDistributions::pick_random_salutation(SalutationsWeights::Female, &mut stream) + .unwrap(); + assert!(!female.is_empty()); + } + + #[test] + fn test_get_first_name_from_index() { + let name = NamesDistributions::get_first_name_from_index(0).unwrap(); + assert!(!name.is_empty()); + } + + #[test] + fn test_deterministic_behavior() { + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let name1 = NamesDistributions::pick_random_first_name( + FirstNamesWeights::GeneralFrequency, + &mut stream1, + ) + .unwrap(); + + let name2 = NamesDistributions::pick_random_first_name( + FirstNamesWeights::GeneralFrequency, + &mut stream2, + ) + .unwrap(); + + assert_eq!(name1, name2); + } +} diff --git a/tpcdsgen/src/distribution/return_reasons_distribution.rs b/tpcdsgen/src/distribution/return_reasons_distribution.rs new file mode 100644 index 00000000..f98d4702 --- /dev/null +++ b/tpcdsgen/src/distribution/return_reasons_distribution.rs @@ -0,0 +1,64 @@ +use crate::distribution::FileBasedStringValuesDistribution; +use crate::error::Result; +use std::sync::OnceLock; + +/// Distribution for return reasons (ReturnReasons) +pub struct ReturnReasonsDistribution; + +impl ReturnReasonsDistribution { + /// Lazy-loaded distribution instance + fn get_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "return_reasons.dst", + 1, + 6, + ) + .expect("Failed to load return_reasons.dst") + }) + } + + /// Get return reason at the specified index (getValueAtIndex) + pub fn get_return_reason_at_index(index: usize) -> Result<&'static str> { + Self::get_distribution().get_value_at_index(0, index) + } + + /// Get the size of the return reasons distribution + pub fn get_size() -> usize { + Self::get_distribution().get_size() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_return_reasons_distribution() { + // Test that we can load the distribution + let size = ReturnReasonsDistribution::get_size(); + assert!( + size > 0, + "Return reasons distribution should have at least one entry" + ); + + // Test that we can get values at valid indices + for i in 0..size.min(5) { + let value = ReturnReasonsDistribution::get_return_reason_at_index(i); + assert!(value.is_ok(), "Should be able to get value at index {}", i); + assert!( + !value.unwrap().is_empty(), + "Value at index {} should not be empty", + i + ); + } + } + + #[test] + fn test_return_reasons_out_of_bounds() { + let size = ReturnReasonsDistribution::get_size(); + let result = ReturnReasonsDistribution::get_return_reason_at_index(size + 100); + assert!(result.is_err(), "Should fail for out of bounds index"); + } +} diff --git a/tpcdsgen/src/distribution/ship_mode_distributions.rs b/tpcdsgen/src/distribution/ship_mode_distributions.rs new file mode 100644 index 00000000..03305d14 --- /dev/null +++ b/tpcdsgen/src/distribution/ship_mode_distributions.rs @@ -0,0 +1,60 @@ +use crate::distribution::FileBasedStringValuesDistribution; +use crate::error::Result; +use std::sync::OnceLock; + +/// Ship mode distributions (ShipModeDistributions) +pub struct ShipModeDistributions; + +impl ShipModeDistributions { + fn get_ship_mode_carrier_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "ship_mode_carrier.dst", + 1, + 1, + ) + .expect("Failed to load ship_mode_carrier.dst") + }) + } + + fn get_ship_mode_code_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "ship_mode_code.dst", + 1, + 1, + ) + .expect("Failed to load ship_mode_code.dst") + }) + } + + fn get_ship_mode_type_distribution() -> &'static FileBasedStringValuesDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + FileBasedStringValuesDistribution::build_string_values_distribution( + "ship_mode_type.dst", + 1, + 1, + ) + .expect("Failed to load ship_mode_type.dst") + }) + } + + pub fn get_ship_mode_carrier_at_index(index: usize) -> Result<&'static str> { + Self::get_ship_mode_carrier_distribution().get_value_at_index(0, index) + } + + pub fn get_ship_mode_code_for_index_mod_size(index: i64) -> Result<&'static str> { + Self::get_ship_mode_code_distribution().get_value_for_index_mod_size(index, 0) + } + + pub fn get_ship_mode_type_for_index_mod_size(index: i64) -> Result<&'static str> { + Self::get_ship_mode_type_distribution().get_value_for_index_mod_size(index, 0) + } + + pub fn get_ship_mode_type_size() -> usize { + Self::get_ship_mode_type_distribution().get_size() + } +} diff --git a/tpcdsgen/src/distribution/string_values.rs b/tpcdsgen/src/distribution/string_values.rs new file mode 100644 index 00000000..56175d3f --- /dev/null +++ b/tpcdsgen/src/distribution/string_values.rs @@ -0,0 +1,299 @@ +use crate::distribution::{Distribution, DistributionUtils, WeightsBuilder}; +use crate::random::RandomNumberStream; +use crate::{error::Result, TpcdsError}; + +/// String-based weighted distribution (StringValuesDistribution) +#[derive(Debug, Clone)] +pub struct StringValuesDistribution { + values_lists: Vec>, + weights_lists: Vec>, +} + +impl StringValuesDistribution { + /// Create new distribution with given values and weights lists + pub fn new(values_lists: Vec>, weights_lists: Vec>) -> Result { + // Validate that values and weights lists have same structure + if values_lists.len() != weights_lists.len() { + return Err(TpcdsError::new( + "Values and weights lists must have same number of lists", + )); + } + + for (i, (values, weights)) in values_lists.iter().zip(weights_lists.iter()).enumerate() { + if values.len() != weights.len() { + return Err(TpcdsError::new(&format!( + "Values list {} and weights list {} must have same length", + i, i + ))); + } + } + + Ok(StringValuesDistribution { + values_lists, + weights_lists, + }) + } + + /// Create distribution from embedded data (for immediate use without files) + pub fn from_embedded_data(data: &[(&str, i32)]) -> Result { + let mut values = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + for (value, weight) in data { + values.push(value.to_string()); + weights_builder.compute_and_add_next_weight(*weight)?; + } + + Ok(StringValuesDistribution { + values_lists: vec![values], + weights_lists: vec![weights_builder.build()], + }) + } + + /// Create distribution from DST-style data with multiple weight columns + pub fn from_multi_weight_data(data: &[(&str, &[i32])]) -> Result { + if data.is_empty() { + return Ok(StringValuesDistribution { + values_lists: vec![], + weights_lists: vec![], + }); + } + + let num_weight_columns = data[0].1.len(); + let mut values = Vec::new(); + let mut weights_builders: Vec = (0..num_weight_columns) + .map(|_| WeightsBuilder::new()) + .collect(); + + for (value, weights) in data { + if weights.len() != num_weight_columns { + return Err(TpcdsError::new( + "All data entries must have same number of weights", + )); + } + + values.push(value.to_string()); + for (i, &weight) in weights.iter().enumerate() { + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let weights_lists: Vec> = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(StringValuesDistribution { + values_lists: vec![values], + weights_lists, + }) + } + + /// Get number of value lists + pub fn get_value_lists_count(&self) -> usize { + self.values_lists.len() + } + + /// Get number of weight lists + pub fn get_weight_lists_count(&self) -> usize { + self.weights_lists.len() + } +} + +impl Distribution for StringValuesDistribution { + /// Pick random value based on weights (core method matching Java) + fn pick_random_value( + &self, + value_list: usize, + weight_list: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result { + if value_list >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of bounds", + value_list + ))); + } + if weight_list >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of bounds", + weight_list + ))); + } + + let values = &self.values_lists[value_list]; + let weights = &self.weights_lists[weight_list]; + + if values.len() != weights.len() { + return Err(TpcdsError::new( + "Values and weights lists have different lengths", + )); + } + + if values.is_empty() { + return Err(TpcdsError::new("Cannot pick from empty distribution")); + } + + let index = DistributionUtils::pick_random_index_from_weights(weights, stream)?; + if index >= values.len() { + return Err(TpcdsError::new(&format!( + "Selected index {} out of bounds for values", + index + ))); + } + + Ok(values[index].clone()) + } + + /// Get value at specific index + fn get_value_at_index(&self, value_list: usize, index: usize) -> Result { + if value_list >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of bounds", + value_list + ))); + } + + let values = &self.values_lists[value_list]; + if index >= values.len() { + return Err(TpcdsError::new(&format!( + "Index {} out of bounds for values", + index + ))); + } + + Ok(values[index].clone()) + } + + /// Get number of values in a list + fn get_value_count(&self, value_list: usize) -> usize { + if value_list >= self.values_lists.len() { + 0 + } else { + self.values_lists[value_list].len() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_from_embedded_data() { + let data = &[("apple", 30), ("banana", 20), ("cherry", 50)]; + + let dist = StringValuesDistribution::from_embedded_data(data).unwrap(); + assert_eq!(dist.get_value_lists_count(), 1); + assert_eq!(dist.get_weight_lists_count(), 1); + assert_eq!(dist.get_value_count(0), 3); + + // Test value access + assert_eq!(dist.get_value_at_index(0, 0).unwrap(), "apple"); + assert_eq!(dist.get_value_at_index(0, 1).unwrap(), "banana"); + assert_eq!(dist.get_value_at_index(0, 2).unwrap(), "cherry"); + } + + #[test] + fn test_from_multi_weight_data() { + let weights1 = [0, 317000]; + let weights2 = [4031, 4031]; + let weights3 = [2500, 2500]; + let data = &[ + ("", weights1.as_slice()), // Empty string - weight 0 for first, 317000 for second + ("Church", weights2.as_slice()), // Church - weight 4031 for both + ("Main", weights3.as_slice()), // Main - weight 2500 for both + ]; + + let dist = StringValuesDistribution::from_multi_weight_data(data).unwrap(); + assert_eq!(dist.get_value_lists_count(), 1); + assert_eq!(dist.get_weight_lists_count(), 2); // Two weight columns + assert_eq!(dist.get_value_count(0), 3); + } + + #[test] + fn test_pick_random_value() { + let data = &[("rare", 1), ("common", 99)]; + + let dist = StringValuesDistribution::from_embedded_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test multiple picks - should all be valid + for _ in 0..10 { + let value = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(value == "rare" || value == "common"); + } + } + + #[test] + fn test_pick_from_multi_weight() { + let weights1 = [0, 100]; + let weights2 = [50, 0]; + let data = &[ + ("empty", weights1.as_slice()), // Never picked from first weight, always from second + ("value", weights2.as_slice()), // Sometimes picked from first, never from second + ]; + + let dist = StringValuesDistribution::from_multi_weight_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Pick from second weight list (index 1) - should only get "empty" + let value = dist.pick_random_value(0, 1, &mut stream).unwrap(); + assert_eq!(value, "empty"); + + // Note: First weight list would be probabilistic, but we can test it works + let value = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(value == "empty" || value == "value"); + } + + #[test] + fn test_deterministic_behavior() { + let data = &[("first", 25), ("second", 25), ("third", 25), ("fourth", 25)]; + + let dist = StringValuesDistribution::from_embedded_data(data).unwrap(); + + // Same seed should produce same results + let mut stream1 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new_with_column(42, 1).unwrap(); + + let value1 = dist.pick_random_value(0, 0, &mut stream1).unwrap(); + let value2 = dist.pick_random_value(0, 0, &mut stream2).unwrap(); + + assert_eq!(value1, value2); + } + + #[test] + fn test_error_conditions() { + let data = &[("test", 100)]; + let dist = StringValuesDistribution::from_embedded_data(data).unwrap(); + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Invalid list indices + assert!(dist.pick_random_value(1, 0, &mut stream).is_err()); // Invalid value list + assert!(dist.pick_random_value(0, 1, &mut stream).is_err()); // Invalid weight list + assert!(dist.get_value_at_index(1, 0).is_err()); // Invalid value list + assert!(dist.get_value_at_index(0, 1).is_err()); // Invalid index + + // Empty distribution + let empty_dist = StringValuesDistribution::new(vec![], vec![]).unwrap(); + assert!(empty_dist.pick_random_value(0, 0, &mut stream).is_err()); + } + + #[test] + fn test_validation() { + // Mismatched list counts + assert!(StringValuesDistribution::new( + vec![vec!["a".to_string()]], + vec![] // Empty weights but non-empty values + ) + .is_err()); + + // Mismatched value/weight lengths + assert!(StringValuesDistribution::new( + vec![vec!["a".to_string(), "b".to_string()]], // 2 values + vec![vec![100]] // 1 weight + ) + .is_err()); + } +} diff --git a/tpcdsgen/src/distribution/string_values_distribution.rs b/tpcdsgen/src/distribution/string_values_distribution.rs new file mode 100644 index 00000000..ecac9a81 --- /dev/null +++ b/tpcdsgen/src/distribution/string_values_distribution.rs @@ -0,0 +1,273 @@ +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{ + get_value_for_index_mod_size, get_weight_for_index, pick_random_index, pick_random_value, + WeightsBuilder, +}; +use crate::error::{Result, TpcdsError}; +use crate::random::RandomNumberStream; + +/// String values distribution that loads from .dst files +/// StringValuesDistribution functionality +#[derive(Debug, Clone)] +pub struct StringValuesDistribution { + values_lists: Vec>, + weights_lists: Vec>, +} + +impl StringValuesDistribution { + /// Build a StringValuesDistribution from a distribution file + /// + /// # Arguments + /// * `filename` - The .dst file to load + /// * `num_value_fields` - Number of value fields per line + /// * `num_weight_fields` - Number of weight fields per line + pub fn build_string_values_distribution( + filename: &str, + num_value_fields: usize, + num_weight_fields: usize, + ) -> Result { + let parsed_lines = DistributionFileLoader::load_distribution_file(filename)?; + + let mut values_builders: Vec> = vec![Vec::new(); num_value_fields]; + let mut weights_builders: Vec = + vec![WeightsBuilder::new(); num_weight_fields]; + + for (values, weights) in parsed_lines { + if values.len() != num_value_fields { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} values, but it contained {}: {:?}", + num_value_fields, + values.len(), + values + ))); + } + + if weights.len() != num_weight_fields { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weights, but it contained {}: {:?}", + num_weight_fields, + weights.len(), + weights + ))); + } + + // Add values to builders + for (i, value) in values.into_iter().enumerate() { + values_builders[i].push(value); + } + + // Add weights to builders + for (i, weight_str) in weights.into_iter().enumerate() { + let weight: i32 = weight_str.parse().map_err(|e| { + TpcdsError::new(&format!("Failed to parse weight '{}': {}", weight_str, e)) + })?; + weights_builders[i].compute_and_add_next_weight(weight)?; + } + } + + let values_lists = values_builders; + let weights_lists = weights_builders + .into_iter() + .map(|builder| builder.build()) + .collect(); + + Ok(StringValuesDistribution { + values_lists, + weights_lists, + }) + } + + /// Pick a random value from the specified value list using the specified weight list + pub fn pick_random_value( + &self, + value_list_index: usize, + weight_list_index: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result<&str> { + if value_list_index >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of range, max is {}", + value_list_index, + self.values_lists.len() - 1 + ))); + } + + if weight_list_index >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of range, max is {}", + weight_list_index, + self.weights_lists.len() - 1 + ))); + } + + let value = pick_random_value( + &self.values_lists[value_list_index], + &self.weights_lists[weight_list_index], + stream, + )?; + + Ok(value) + } + + /// Get a value by index modulo the size of the list + pub fn get_value_for_index_mod_size( + &self, + index: i64, + value_list_index: usize, + ) -> Result<&str> { + if value_list_index >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of range, max is {}", + value_list_index, + self.values_lists.len() - 1 + ))); + } + + let value = get_value_for_index_mod_size(index, &self.values_lists[value_list_index]); + Ok(value) + } + + /// Pick a random index from the specified weight list + pub fn pick_random_index( + &self, + weight_list_index: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result { + if weight_list_index >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of range, max is {}", + weight_list_index, + self.weights_lists.len() - 1 + ))); + } + + pick_random_index(&self.weights_lists[weight_list_index], stream) + } + + /// Get the weight for a specific index + pub fn get_weight_for_index(&self, index: usize, weight_list_index: usize) -> Result { + if weight_list_index >= self.weights_lists.len() { + return Err(TpcdsError::new(&format!( + "Weight list index {} out of range, max is {}", + weight_list_index, + self.weights_lists.len() - 1 + ))); + } + + get_weight_for_index(index, &self.weights_lists[weight_list_index]) + } + + /// Get the size of the distribution (number of entries) + pub fn get_size(&self) -> usize { + if self.values_lists.is_empty() { + 0 + } else { + self.values_lists[0].len() + } + } + + /// Get a specific value at a specific index + pub fn get_value_at_index(&self, value_list_index: usize, value_index: usize) -> Result<&str> { + if value_list_index >= self.values_lists.len() { + return Err(TpcdsError::new(&format!( + "Value list index {} out of range, max is {}", + value_list_index, + self.values_lists.len() - 1 + ))); + } + + if value_index >= self.values_lists[value_list_index].len() { + return Err(TpcdsError::new(&format!( + "Value index {} out of range, max is {}", + value_index, + self.values_lists[value_list_index].len() - 1 + ))); + } + + Ok(&self.values_lists[value_list_index][value_index]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_build_call_centers_distribution() { + let dist = StringValuesDistribution::build_string_values_distribution( + "call_centers.dst", + 1, // 1 value field (name) + 2, // 2 weight fields (uniform, sales percentage) + ) + .unwrap(); + + assert!(dist.get_size() > 0); + + // Check we can get a value by index + let first_center = dist.get_value_at_index(0, 0).unwrap(); + assert!(!first_center.is_empty()); + } + + #[test] + fn test_pick_random_call_center() { + let dist = StringValuesDistribution::build_string_values_distribution( + "call_centers.dst", + 1, // 1 value field (name) + 2, // 2 weight fields (uniform, sales percentage) + ) + .unwrap(); + + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Pick using uniform weights (index 0) + let center1 = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(!center1.is_empty()); + + // Pick using sales percentage weights (index 1) + let center2 = dist.pick_random_value(0, 1, &mut stream).unwrap(); + assert!(!center2.is_empty()); + } + + #[test] + fn test_first_names_distribution() { + let dist = StringValuesDistribution::build_string_values_distribution( + "first_names.dst", + 1, // 1 value field (name) + 3, // 3 weight fields (male freq, female freq, general freq) + ) + .unwrap(); + + assert!(dist.get_size() > 100); // Should have many names + + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Pick using male frequency weights (index 0) + let male_name = dist.pick_random_value(0, 0, &mut stream).unwrap(); + assert!(!male_name.is_empty()); + + // Pick using female frequency weights (index 1) + let female_name = dist.pick_random_value(0, 1, &mut stream).unwrap(); + assert!(!female_name.is_empty()); + + // Pick using general frequency weights (index 2) + let general_name = dist.pick_random_value(0, 2, &mut stream).unwrap(); + assert!(!general_name.is_empty()); + } + + #[test] + fn test_deterministic_selection() { + let dist = + StringValuesDistribution::build_string_values_distribution("call_centers.dst", 1, 2) + .unwrap(); + + let mut stream1 = RandomNumberStreamImpl::new(1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(1).unwrap(); + + let result1 = dist.pick_random_value(0, 0, &mut stream1).unwrap(); + let result2 = dist.pick_random_value(0, 0, &mut stream2).unwrap(); + + // Should be deterministic with same seed + assert_eq!(result1, result2); + } +} diff --git a/tpcdsgen/src/distribution/top_domains_distribution.rs b/tpcdsgen/src/distribution/top_domains_distribution.rs new file mode 100644 index 00000000..8dcf3903 --- /dev/null +++ b/tpcdsgen/src/distribution/top_domains_distribution.rs @@ -0,0 +1,176 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Top domains distribution for web_site table generation. +//! +//! This module provides distribution of top-level domain suffixes (com, org, edu) +//! with uniform weighted random selection. + +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Top domains distribution (TopDomainsDistribution) +/// +/// Loads top_domains.dst which contains: +/// - 1 value field: domain suffix (com, org, edu, etc.) +/// - 1 weight field: uniform weights +pub struct TopDomainsDistribution { + values: Vec, // Domain suffixes + weights_list: Vec, // Uniform weights +} + +impl TopDomainsDistribution { + const NUM_VALUE_FIELDS: usize = 1; + const NUM_WEIGHT_FIELDS: usize = 1; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "top_domains.dst"; + + fn get_instance() -> &'static TopDomainsDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_top_domains_distribution().expect("Failed to load top domains distribution") + }) + } + + fn build_top_domains_distribution() -> Result { + let mut values = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (value_fields, weight_fields) in parsed_lines { + if value_fields.len() != Self::NUM_VALUE_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} value field, but it contained {}: {:?}", + Self::NUM_VALUE_FIELDS, + value_fields.len(), + value_fields + ))); + } + + if weight_fields.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weight field, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weight_fields.len(), + weight_fields + ))); + } + + // Parse value (domain suffix) + values.push(value_fields[0].trim().to_string()); + + // Parse weight + let weight: i32 = weight_fields[0].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight '{}': {}", + weight_fields[0], e + )) + })?; + weights_builder.compute_and_add_next_weight(weight)?; + } + + Ok(TopDomainsDistribution { + values, + weights_list: weights_builder.build(), + }) + } + + /// Pick a random top-level domain suffix. + /// + /// This corresponds to TopDomainsDistribution.pickRandomTopDomain() + /// + /// # Arguments + /// + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// A domain suffix string (e.g., "com", "org", "edu") + pub fn pick_random_top_domain(stream: &mut dyn RandomNumberStream) -> Result { + let dist = Self::get_instance(); + let value_ref = pick_random_value(&dist.values, &dist.weights_list, stream)?; + Ok(value_ref.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_top_domains_distribution_loading() { + let dist = TopDomainsDistribution::get_instance(); + + // Should have at least 3 domain types: com, org, edu + assert!(dist.values.len() >= 3, "Should have at least 3 top domains"); + assert_eq!(dist.weights_list.len(), dist.values.len()); + } + + #[test] + fn test_pick_random_top_domain() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let domain = TopDomainsDistribution::pick_random_top_domain(&mut stream).unwrap(); + + // Should be a non-empty string + assert!(!domain.is_empty(), "Domain should not be empty"); + + // Typical domains are short + assert!( + domain.len() <= 10, + "Domain suffix '{}' should be reasonably short", + domain + ); + } + + #[test] + fn test_pick_random_top_domain_deterministic() { + // Same seed should produce same result + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let domain1 = TopDomainsDistribution::pick_random_top_domain(&mut stream1).unwrap(); + let domain2 = TopDomainsDistribution::pick_random_top_domain(&mut stream2).unwrap(); + + assert_eq!(domain1, domain2, "Same seed should produce same domain"); + } + + #[test] + fn test_domain_values() { + let dist = TopDomainsDistribution::get_instance(); + + // Verify expected domains are present + let domains_set: std::collections::HashSet<&String> = dist.values.iter().collect(); + assert!( + domains_set.contains(&"com".to_string()), + "Should contain 'com' domain" + ); + } + + #[test] + fn test_multiple_picks_are_valid() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Pick multiple domains and verify all are valid + for _ in 0..10 { + let domain = TopDomainsDistribution::pick_random_top_domain(&mut stream).unwrap(); + assert!(!domain.is_empty(), "All picked domains should be non-empty"); + } + } +} diff --git a/tpcdsgen/src/distribution/utils.rs b/tpcdsgen/src/distribution/utils.rs new file mode 100644 index 00000000..2f51c173 --- /dev/null +++ b/tpcdsgen/src/distribution/utils.rs @@ -0,0 +1,347 @@ +use crate::random::RandomNumberStream; +use crate::{check_argument, error::Result, TpcdsError}; + +/// Core trait for weighted distributions +pub trait Distribution { + /// Pick a random value based on weights + fn pick_random_value( + &self, + value_list: usize, + weight_list: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result; + + /// Get value at specific index + fn get_value_at_index(&self, value_list: usize, index: usize) -> Result; + + /// Get number of values in a list + fn get_value_count(&self, value_list: usize) -> usize; +} + +/// Builds cumulative weights for weighted selection (WeightsBuilder) +#[derive(Debug, Clone)] +pub struct WeightsBuilder { + weights: Vec, + previous_weight: i32, +} + +impl WeightsBuilder { + pub fn new() -> Self { + WeightsBuilder { + weights: Vec::new(), + previous_weight: 0, + } + } + + /// Compute and add next cumulative weight + pub fn compute_and_add_next_weight(&mut self, weight: i32) -> Result<&mut Self> { + check_argument!(weight >= 0, "Weight cannot be negative."); + let new_weight = self.previous_weight + weight; + self.weights.push(new_weight); + self.previous_weight = new_weight; + Ok(self) + } + + /// Build final immutable weights list + pub fn build(self) -> Vec { + self.weights + } + + /// Get current total weight + pub fn get_total_weight(&self) -> i32 { + self.previous_weight + } +} + +/// Pick a random value from values list based on weights (DistributionUtils.pickRandomValue) +pub fn pick_random_value<'a, T>( + values: &'a [T], + weights: &[i32], + stream: &mut dyn RandomNumberStream, +) -> Result<&'a T> { + use crate::random::RandomValueGenerator; + + if values.len() != weights.len() { + return Err(TpcdsError::new( + "Values and weights lists must be the same size", + )); + } + + if weights.is_empty() { + return Err(TpcdsError::new("Cannot pick from empty distribution")); + } + + let max_weight = weights[weights.len() - 1]; + let random_weight = RandomValueGenerator::generate_uniform_random_int(1, max_weight, stream); + + get_value_for_weight(random_weight, values, weights) +} + +/// Get value for specific weight (DistributionUtils.getValueForWeight) +fn get_value_for_weight<'a, T>(weight: i32, values: &'a [T], weights: &[i32]) -> Result<&'a T> { + if values.len() != weights.len() { + return Err(TpcdsError::new( + "Values and weights lists must be the same size", + )); + } + + for (index, &w) in weights.iter().enumerate() { + if weight <= w { + return Ok(&values[index]); + } + } + + Err(TpcdsError::new("Random weight was greater than max weight")) +} + +/// Get value for index modulo size (DistributionUtils.getValueForIndexModSize) +pub fn get_value_for_index_mod_size(index: i64, values: &[T]) -> &T { + let size = values.len() as i64; + let index_mod_size = (index % size) as usize; + &values[index_mod_size] +} + +/// Pick random index from weights (DistributionUtils.pickRandomIndex) +pub fn pick_random_index(weights: &[i32], stream: &mut dyn RandomNumberStream) -> Result { + use crate::random::RandomValueGenerator; + + if weights.is_empty() { + return Err(TpcdsError::new("Cannot pick from empty weights")); + } + + let max_weight = weights[weights.len() - 1]; + let random_weight = RandomValueGenerator::generate_uniform_random_int(1, max_weight, stream); + + get_index_for_weight(random_weight, weights) +} + +/// Get index for specific weight (DistributionUtils.getIndexForWeight) +fn get_index_for_weight(weight: i32, weights: &[i32]) -> Result { + for (index, &w) in weights.iter().enumerate() { + if weight <= w { + return Ok(index); + } + } + + Err(TpcdsError::new("Random weight was greater than max weight")) +} + +/// Get weight for specific index (DistributionUtils.getWeightForIndex) +pub fn get_weight_for_index(index: usize, weights: &[i32]) -> Result { + if index >= weights.len() { + return Err(TpcdsError::new(&format!( + "Index {} larger than distribution size {}", + index, + weights.len() + ))); + } + + // Reverse the accumulation of weights + if index == 0 { + Ok(weights[index]) + } else { + Ok(weights[index] - weights[index - 1]) + } +} + +impl Default for WeightsBuilder { + fn default() -> Self { + Self::new() + } +} + +/// Core distribution utilities (DistributionUtils) +pub struct DistributionUtils; + +impl DistributionUtils { + /// Pick random index based on cumulative weights (core algorithm from Java) + pub fn pick_random_index_from_weights( + weights: &[i32], + stream: &mut dyn RandomNumberStream, + ) -> Result { + if weights.is_empty() { + return Err(TpcdsError::new("Cannot pick from empty weights")); + } + + let max_weight = *weights.last().unwrap(); + if max_weight <= 0 { + return Err(TpcdsError::new("Total weight must be positive")); + } + + // Generate random number in range [1, max_weight] (inclusive) + // This matches the Java implementation exactly + let random_weight = + crate::random::RandomValueGenerator::generate_uniform_random_int(1, max_weight, stream); + + // Find first weight >= random_weight using binary search + // This is the cumulative weight distribution selection algorithm + match weights.binary_search(&random_weight) { + Ok(index) => Ok(index), + Err(index) => { + // binary_search returns insertion point when not found + // This is exactly where the random_weight would fall + if index < weights.len() { + Ok(index) + } else { + // Should not happen with proper weights, but handle gracefully + Ok(weights.len() - 1) + } + } + } + } + + /// Pick random index with uniform distribution (for non-weighted selection) + pub fn pick_random_index_uniform( + count: usize, + stream: &mut dyn RandomNumberStream, + ) -> Result { + if count == 0 { + return Err(TpcdsError::new("Cannot pick from empty collection")); + } + + let index = crate::random::RandomValueGenerator::generate_uniform_random_int( + 0, + count as i32 - 1, + stream, + ); + Ok(index as usize) + } + + /// Parse comma-separated list of values (utility for file parsing) + pub fn parse_comma_separated_values(line: &str) -> Vec { + line.split(',').map(|s| s.trim().to_string()).collect() + } + + /// Parse colon-separated value and weight(s) from .dst format + pub fn parse_dst_line(line: &str) -> Result<(String, Vec)> { + let line = line.trim(); + + // Skip empty lines and comments + if line.is_empty() || line.starts_with("--") || line.starts_with("//") { + return Err(TpcdsError::new("Skip comment or empty line")); + } + + let parts: Vec<&str> = line.split(':').collect(); + if parts.len() != 2 { + return Err(TpcdsError::new(&format!( + "Invalid .dst line format: {}", + line + ))); + } + + let value = parts[0].trim().to_string(); + let weights_str = parts[1].trim(); + + // Parse weights (can be single or comma-separated) + let weights: Result> = weights_str + .split(',') + .map(|w| { + w.trim() + .parse::() + .map_err(|_| TpcdsError::new(&format!("Invalid weight: {}", w))) + }) + .collect(); + + Ok((value, weights?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_weights_builder() { + let mut builder = WeightsBuilder::new(); + builder.compute_and_add_next_weight(10).unwrap(); + builder.compute_and_add_next_weight(20).unwrap(); + builder.compute_and_add_next_weight(30).unwrap(); + + let weights = builder.build(); + assert_eq!(weights, vec![10, 30, 60]); // Cumulative weights + } + + #[test] + fn test_weights_builder_negative_weight() { + let mut builder = WeightsBuilder::new(); + assert!(builder.compute_and_add_next_weight(-5).is_err()); + } + + #[test] + fn test_pick_random_index_from_weights() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let weights = vec![10, 30, 60, 100]; // Cumulative weights + + // Test multiple selections to ensure they're in valid range + for _ in 0..10 { + let index = + DistributionUtils::pick_random_index_from_weights(&weights, &mut stream).unwrap(); + assert!(index < weights.len()); + } + } + + #[test] + fn test_pick_random_index_uniform() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + for _ in 0..10 { + let index = DistributionUtils::pick_random_index_uniform(5, &mut stream).unwrap(); + assert!(index < 5); + } + } + + #[test] + fn test_pick_random_index_empty() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + assert!(DistributionUtils::pick_random_index_from_weights(&[], &mut stream).is_err()); + assert!(DistributionUtils::pick_random_index_uniform(0, &mut stream).is_err()); + } + + #[test] + fn test_parse_comma_separated_values() { + let result = DistributionUtils::parse_comma_separated_values("a, b , c,d"); + assert_eq!(result, vec!["a", "b", "c", "d"]); + } + + #[test] + fn test_parse_dst_line() { + // Simple value:weight + let (value, weights) = DistributionUtils::parse_dst_line("then:619").unwrap(); + assert_eq!(value, "then"); + assert_eq!(weights, vec![619]); + + // Multi-weight + let (value, weights) = DistributionUtils::parse_dst_line(": 0, 317000").unwrap(); + assert_eq!(value, ""); + assert_eq!(weights, vec![0, 317000]); + + // Value with spaces + let (value, weights) = DistributionUtils::parse_dst_line("Church: 4031, 4031").unwrap(); + assert_eq!(value, "Church"); + assert_eq!(weights, vec![4031, 4031]); + + // Comments should error + assert!(DistributionUtils::parse_dst_line("-- comment").is_err()); + assert!(DistributionUtils::parse_dst_line("").is_err()); + + // Invalid format + assert!(DistributionUtils::parse_dst_line("invalid_format").is_err()); + } + + #[test] + fn test_deterministic_selection() { + // Test that same seed produces same results + let weights = vec![25, 50, 75, 100]; + + let mut stream1 = RandomNumberStreamImpl::new_with_column(1, 1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new_with_column(1, 1).unwrap(); + + let index1 = + DistributionUtils::pick_random_index_from_weights(&weights, &mut stream1).unwrap(); + let index2 = + DistributionUtils::pick_random_index_from_weights(&weights, &mut stream2).unwrap(); + + assert_eq!(index1, index2); // Should be deterministic + } +} diff --git a/tpcdsgen/src/distribution/web_page_use_distribution.rs b/tpcdsgen/src/distribution/web_page_use_distribution.rs new file mode 100644 index 00000000..73cb3f93 --- /dev/null +++ b/tpcdsgen/src/distribution/web_page_use_distribution.rs @@ -0,0 +1,210 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web page use distribution for web_page table generation. +//! +//! This module provides distribution of web page types (general, order, welcome, etc.) +//! with uniform weighted random selection. + +use crate::distribution::file_loader::DistributionFileLoader; +use crate::distribution::utils::{pick_random_value, WeightsBuilder}; +use crate::error::Result; +use crate::random::RandomNumberStream; +use crate::TpcdsError; +use std::sync::OnceLock; + +/// Web page use distribution (WebPageUseDistribution) +/// +/// Loads web_page_use.dst which contains: +/// - 1 value field: page use type (general, order, welcome, ad, feedback, protected, dynamic) +/// - 1 weight field: uniform weights +pub struct WebPageUseDistribution { + values: Vec, // Page use types + weights_list: Vec, // Uniform weights +} + +impl WebPageUseDistribution { + const NUM_VALUE_FIELDS: usize = 1; + const NUM_WEIGHT_FIELDS: usize = 1; + const VALUES_AND_WEIGHTS_FILENAME: &'static str = "web_page_use.dst"; + + fn get_instance() -> &'static WebPageUseDistribution { + static DISTRIBUTION: OnceLock = OnceLock::new(); + DISTRIBUTION.get_or_init(|| { + Self::build_web_page_use_distribution() + .expect("Failed to load web page use distribution") + }) + } + + fn build_web_page_use_distribution() -> Result { + let mut values = Vec::new(); + let mut weights_builder = WeightsBuilder::new(); + + let parsed_lines = + DistributionFileLoader::load_distribution_file(Self::VALUES_AND_WEIGHTS_FILENAME)?; + + for (value_fields, weight_fields) in parsed_lines { + if value_fields.len() != Self::NUM_VALUE_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} value field, but it contained {}: {:?}", + Self::NUM_VALUE_FIELDS, + value_fields.len(), + value_fields + ))); + } + + if weight_fields.len() != Self::NUM_WEIGHT_FIELDS { + return Err(TpcdsError::new(&format!( + "Expected line to contain {} weight field, but it contained {}: {:?}", + Self::NUM_WEIGHT_FIELDS, + weight_fields.len(), + weight_fields + ))); + } + + // Parse value (page use type) + values.push(value_fields[0].trim().to_string()); + + // Parse weight + let weight: i32 = weight_fields[0].parse().map_err(|e| { + TpcdsError::new(&format!( + "Failed to parse weight '{}': {}", + weight_fields[0], e + )) + })?; + weights_builder.compute_and_add_next_weight(weight)?; + } + + Ok(WebPageUseDistribution { + values, + weights_list: weights_builder.build(), + }) + } + + /// Pick a random web page use type. + /// + /// This corresponds to WebPageUseDistribution.pickRandomWebPageUseType() + /// + /// # Arguments + /// + /// * `stream` - Random number stream for generating random values + /// + /// # Returns + /// + /// A web page use type string (e.g., "general", "order", "welcome", "ad", "feedback", "protected", "dynamic") + pub fn pick_random_web_page_use_type(stream: &mut dyn RandomNumberStream) -> Result { + let dist = Self::get_instance(); + let value_ref = pick_random_value(&dist.values, &dist.weights_list, stream)?; + Ok(value_ref.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_web_page_use_distribution_loading() { + let dist = WebPageUseDistribution::get_instance(); + + // Should have 7 page use types: general, order, welcome, ad, feedback, protected, dynamic + assert_eq!(dist.values.len(), 7, "Should have 7 web page use types"); + assert_eq!(dist.weights_list.len(), 7); + } + + #[test] + fn test_pick_random_web_page_use_type() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let page_use = WebPageUseDistribution::pick_random_web_page_use_type(&mut stream).unwrap(); + + // Should be a non-empty string + assert!(!page_use.is_empty(), "Page use type should not be empty"); + + // Should be one of the expected types + let valid_types = [ + "general", + "order", + "welcome", + "ad", + "feedback", + "protected", + "dynamic", + ]; + assert!( + valid_types.contains(&page_use.as_str()), + "Page use '{}' should be one of: {:?}", + page_use, + valid_types + ); + } + + #[test] + fn test_pick_random_web_page_use_type_deterministic() { + // Same seed should produce same result + let mut stream1 = RandomNumberStreamImpl::new(42).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(42).unwrap(); + + let page_use1 = + WebPageUseDistribution::pick_random_web_page_use_type(&mut stream1).unwrap(); + let page_use2 = + WebPageUseDistribution::pick_random_web_page_use_type(&mut stream2).unwrap(); + + assert_eq!( + page_use1, page_use2, + "Same seed should produce same page use type" + ); + } + + #[test] + fn test_page_use_values() { + let dist = WebPageUseDistribution::get_instance(); + + // Verify expected page use types are present + let types_set: std::collections::HashSet<&String> = dist.values.iter().collect(); + assert!( + types_set.contains(&"general".to_string()), + "Should contain 'general' type" + ); + assert!( + types_set.contains(&"order".to_string()), + "Should contain 'order' type" + ); + } + + #[test] + fn test_multiple_picks_are_valid() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let valid_types = [ + "general", + "order", + "welcome", + "ad", + "feedback", + "protected", + "dynamic", + ]; + + // Pick multiple page use types and verify all are valid + for _ in 0..20 { + let page_use = + WebPageUseDistribution::pick_random_web_page_use_type(&mut stream).unwrap(); + assert!( + valid_types.contains(&page_use.as_str()), + "Page use '{}' should be valid", + page_use + ); + } + } +} diff --git a/tpcdsgen/src/error.rs b/tpcdsgen/src/error.rs new file mode 100644 index 00000000..93e24c57 --- /dev/null +++ b/tpcdsgen/src/error.rs @@ -0,0 +1,98 @@ +#[derive(Debug, Clone, PartialEq)] +pub struct TpcdsError { + message: String, +} + +impl TpcdsError { + pub fn new(message: &str) -> Self { + Self { + message: message.to_string(), + } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl std::fmt::Display for TpcdsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for TpcdsError {} + +pub type Result = std::result::Result; + +/// Specific error for invalid command-line options +#[derive(Debug, Clone, PartialEq)] +pub struct InvalidOptionError { + option_name: String, + value: String, + message: String, +} + +impl InvalidOptionError { + pub fn new(option_name: &str, value: &str) -> Self { + Self::with_message(option_name, value, "") + } + + pub fn with_message(option_name: &str, value: &str, message: &str) -> Self { + Self { + option_name: option_name.to_string(), + value: value.to_string(), + message: message.to_string(), + } + } + + pub fn option_name(&self) -> &str { + &self.option_name + } + + pub fn value(&self) -> &str { + &self.value + } + + pub fn custom_message(&self) -> &str { + &self.message + } +} + +impl std::fmt::Display for InvalidOptionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Invalid value for {}: '{}'. {}", + self.option_name, self.value, self.message + ) + } +} + +impl std::error::Error for InvalidOptionError {} + +impl From for TpcdsError { + fn from(err: InvalidOptionError) -> Self { + TpcdsError::new(&err.to_string()) + } +} + +// Utility macros for argument validation (similar to Java's checkArgument) +#[macro_export] +macro_rules! check_argument { + ($condition:expr, $message:expr) => { + #[allow(clippy::neg_cmp_op_on_partial_ord)] + if !$condition { + return Err(TpcdsError::new($message)); + } + }; +} + +#[macro_export] +macro_rules! check_state { + ($condition:expr, $message:expr) => { + if !$condition { + return Err(TpcdsError::new($message)); + } + }; +} diff --git a/tpcdsgen/src/generator/call_center_generator_column.rs b/tpcdsgen/src/generator/call_center_generator_column.rs new file mode 100644 index 00000000..a6a24641 --- /dev/null +++ b/tpcdsgen/src/generator/call_center_generator_column.rs @@ -0,0 +1,262 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Call Center generator columns (CallCenterGeneratorColumn enum) +/// These are used internally by the generator and may include non-visible columns +/// or omit visible columns that get derived from other columns. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CallCenterGeneratorColumn { + CcCallCenterSk, + CcCallCenterId, + CcRecStartDateId, + CcRecEndDateId, + CcClosedDateId, + CcOpenDateId, + CcName, + CcClass, + CcEmployees, + CcSqFt, + CcHours, + CcManager, + CcMarketId, + CcMarketClass, + CcMarketDesc, + CcMarketManager, + CcDivision, + CcDivisionName, + CcCompany, + CcCompanyName, + CcStreetNumber, + CcStreetName, + CcStreetType, + CcSuiteNumber, + CcCity, + CcCounty, + CcState, + CcZip, + CcCountry, + CcGmtOffset, + CcAddress, + CcTaxPercentage, + CcScd, + CcNulls, +} + +impl CallCenterGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [CallCenterGeneratorColumn] { + use CallCenterGeneratorColumn::*; + static VALUES: &[CallCenterGeneratorColumn] = &[ + CcCallCenterSk, + CcCallCenterId, + CcRecStartDateId, + CcRecEndDateId, + CcClosedDateId, + CcOpenDateId, + CcName, + CcClass, + CcEmployees, + CcSqFt, + CcHours, + CcManager, + CcMarketId, + CcMarketClass, + CcMarketDesc, + CcMarketManager, + CcDivision, + CcDivisionName, + CcCompany, + CcCompanyName, + CcStreetNumber, + CcStreetName, + CcStreetType, + CcSuiteNumber, + CcCity, + CcCounty, + CcState, + CcZip, + CcCountry, + CcGmtOffset, + CcAddress, + CcTaxPercentage, + CcScd, + CcNulls, + ]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + /// Values exactly match Java implementation + fn get_column_info(&self) -> (i32, i32) { + use CallCenterGeneratorColumn::*; + match self { + CcCallCenterSk => (1, 0), + CcCallCenterId => (2, 15), + CcRecStartDateId => (3, 10), + CcRecEndDateId => (4, 1), + CcClosedDateId => (5, 4), + CcOpenDateId => (6, 10), + CcName => (7, 0), + CcClass => (8, 2), + CcEmployees => (9, 1), + CcSqFt => (10, 1), + CcHours => (11, 1), + CcManager => (12, 2), + CcMarketId => (13, 1), + CcMarketClass => (14, 50), + CcMarketDesc => (15, 50), + CcMarketManager => (16, 2), + CcDivision => (17, 2), + CcDivisionName => (18, 2), + CcCompany => (19, 2), + CcCompanyName => (20, 2), + CcStreetNumber => (21, 0), + CcStreetName => (22, 0), + CcStreetType => (23, 0), + CcSuiteNumber => (24, 0), + CcCity => (25, 0), + CcCounty => (26, 0), + CcState => (27, 0), + CcZip => (28, 0), + CcCountry => (29, 0), + CcGmtOffset => (30, 0), + CcAddress => (31, 15), + CcTaxPercentage => (32, 1), + CcScd => (33, 1), + CcNulls => (34, 2), + } + } +} + +impl GeneratorColumn for CallCenterGeneratorColumn { + fn get_table(&self) -> Table { + Table::CallCenter + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_call_center_generator_column_basics() { + let column = CallCenterGeneratorColumn::CcCallCenterSk; + assert_eq!(column.get_table(), Table::CallCenter); + assert_eq!(column.get_global_column_number(), 1); + assert_eq!(column.get_seeds_per_row(), 0); + } + + #[test] + fn test_generator_column_count() { + let columns = CallCenterGeneratorColumn::values(); + assert_eq!(columns.len(), 34); // 31 regular columns + 3 special generator columns + } + + #[test] + fn test_global_column_numbers() { + let columns = CallCenterGeneratorColumn::values(); + + // Test first few columns match Java exactly + assert_eq!(columns[0].get_global_column_number(), 1); // CcCallCenterSk + assert_eq!(columns[1].get_global_column_number(), 2); // CcCallCenterId + assert_eq!(columns[2].get_global_column_number(), 3); // CcRecStartDateId + + // Test last column + assert_eq!(columns[33].get_global_column_number(), 34); // CcNulls + } + + #[test] + fn test_seeds_per_row() { + // Test some key columns with different seed counts + assert_eq!( + CallCenterGeneratorColumn::CcCallCenterSk.get_seeds_per_row(), + 0 + ); + assert_eq!( + CallCenterGeneratorColumn::CcCallCenterId.get_seeds_per_row(), + 15 + ); + assert_eq!( + CallCenterGeneratorColumn::CcRecStartDateId.get_seeds_per_row(), + 10 + ); + assert_eq!( + CallCenterGeneratorColumn::CcMarketClass.get_seeds_per_row(), + 50 + ); + assert_eq!(CallCenterGeneratorColumn::CcAddress.get_seeds_per_row(), 15); + } + + #[test] + fn test_special_generator_columns() { + // Test columns that exist in generator but not in regular Column enum + let address_col = CallCenterGeneratorColumn::CcAddress; + assert_eq!(address_col.get_global_column_number(), 31); + assert_eq!(address_col.get_seeds_per_row(), 15); + + let scd_col = CallCenterGeneratorColumn::CcScd; + assert_eq!(scd_col.get_global_column_number(), 33); + assert_eq!(scd_col.get_seeds_per_row(), 1); + + let nulls_col = CallCenterGeneratorColumn::CcNulls; + assert_eq!(nulls_col.get_global_column_number(), 34); + assert_eq!(nulls_col.get_seeds_per_row(), 2); + } + + #[test] + fn test_all_columns_have_table() { + for column in CallCenterGeneratorColumn::values() { + assert_eq!(column.get_table(), Table::CallCenter); + } + } + + #[test] + fn test_column_ordering() { + let columns = CallCenterGeneratorColumn::values(); + + // Verify global column numbers are sequential + for (i, column) in columns.iter().enumerate() { + assert_eq!(column.get_global_column_number(), (i + 1) as i32); + } + } + + #[test] + fn test_debug_display() { + let column = CallCenterGeneratorColumn::CcCallCenterSk; + let debug_str = format!("{:?}", column); + assert_eq!(debug_str, "CcCallCenterSk"); + } + + #[test] + fn test_generator_vs_regular_columns() { + // Generator columns should include some columns not in regular Column enum + let generator_count = CallCenterGeneratorColumn::values().len(); + + // We know regular CallCenterColumn has 31 columns, generator has 34 + // The extra ones are CcAddress, CcScd, CcNulls + assert_eq!(generator_count, 34); + + // Verify the extra columns exist + let has_address = CallCenterGeneratorColumn::values() + .iter() + .any(|&col| matches!(col, CallCenterGeneratorColumn::CcAddress)); + let has_scd = CallCenterGeneratorColumn::values() + .iter() + .any(|&col| matches!(col, CallCenterGeneratorColumn::CcScd)); + let has_nulls = CallCenterGeneratorColumn::values() + .iter() + .any(|&col| matches!(col, CallCenterGeneratorColumn::CcNulls)); + + assert!(has_address); + assert!(has_scd); + assert!(has_nulls); + } +} diff --git a/tpcdsgen/src/generator/catalog_page_generator_column.rs b/tpcdsgen/src/generator/catalog_page_generator_column.rs new file mode 100644 index 00000000..af424ed6 --- /dev/null +++ b/tpcdsgen/src/generator/catalog_page_generator_column.rs @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog page generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the catalog_page table +/// Global column numbers 35-45 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CatalogPageGeneratorColumn { + CpCatalogPageSk, // 35 + CpCatalogPageId, // 36 + CpStartDateId, // 37 + CpEndDateId, // 38 + CpPromoId, // 39 (unused) + CpDepartment, // 40 + CpCatalogNumber, // 41 + CpCatalogPageNumber, // 42 + CpDescription, // 43 + CpType, // 44 + CpNulls, // 45 +} + +impl GeneratorColumn for CatalogPageGeneratorColumn { + fn get_table(&self) -> Table { + Table::CatalogPage + } + + fn get_global_column_number(&self) -> i32 { + match self { + CatalogPageGeneratorColumn::CpCatalogPageSk => 35, + CatalogPageGeneratorColumn::CpCatalogPageId => 36, + CatalogPageGeneratorColumn::CpStartDateId => 37, + CatalogPageGeneratorColumn::CpEndDateId => 38, + CatalogPageGeneratorColumn::CpPromoId => 39, + CatalogPageGeneratorColumn::CpDepartment => 40, + CatalogPageGeneratorColumn::CpCatalogNumber => 41, + CatalogPageGeneratorColumn::CpCatalogPageNumber => 42, + CatalogPageGeneratorColumn::CpDescription => 43, + CatalogPageGeneratorColumn::CpType => 44, + CatalogPageGeneratorColumn::CpNulls => 45, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + CatalogPageGeneratorColumn::CpDescription => 100, // S_CP_DESCRIPTION + CatalogPageGeneratorColumn::CpNulls => 2, + _ => 1, + } + } +} + +impl CatalogPageGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [CatalogPageGeneratorColumn] { + use CatalogPageGeneratorColumn::*; + &[ + CpCatalogPageSk, + CpCatalogPageId, + CpStartDateId, + CpEndDateId, + CpPromoId, + CpDepartment, + CpCatalogNumber, + CpCatalogPageNumber, + CpDescription, + CpType, + CpNulls, + ] + } +} diff --git a/tpcdsgen/src/generator/catalog_returns_generator_column.rs b/tpcdsgen/src/generator/catalog_returns_generator_column.rs new file mode 100644 index 00000000..0696c53e --- /dev/null +++ b/tpcdsgen/src/generator/catalog_returns_generator_column.rs @@ -0,0 +1,165 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog returns generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the catalog_returns table +/// Global column numbers 46-74 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CatalogReturnsGeneratorColumn { + CrReturnedDateSk, // 46 + CrReturnedTimeSk, // 47 + CrItemSk, // 48 + CrRefundedCustomerSk, // 49 + CrRefundedCdemoSk, // 50 + CrRefundedHdemoSk, // 51 + CrRefundedAddrSk, // 52 + CrReturningCustomerSk, // 53 + CrReturningCdemoSk, // 54 + CrReturningHdemoSk, // 55 + CrReturningAddrSk, // 56 + CrCallCenterSk, // 57 + CrCatalogPageSk, // 58 + CrShipModeSk, // 59 + CrWarehouseSk, // 60 + CrReasonSk, // 61 + CrOrderNumber, // 62 + CrPricingQuantity, // 63 + CrPricingNetPaid, // 64 + CrPricingExtTax, // 65 + CrPricingNetPaidIncTax, // 66 + CrPricingFee, // 67 + CrPricingExtShipCost, // 68 + CrPricingRefundedCash, // 69 + CrPricingReversedCharge, // 70 + CrPricingStoreCredit, // 71 + CrPricingNetLoss, // 72 + CrNulls, // 73 + CrPricing, // 74 +} + +impl GeneratorColumn for CatalogReturnsGeneratorColumn { + fn get_table(&self) -> Table { + Table::CatalogReturns + } + + fn get_global_column_number(&self) -> i32 { + match self { + CatalogReturnsGeneratorColumn::CrReturnedDateSk => 46, + CatalogReturnsGeneratorColumn::CrReturnedTimeSk => 47, + CatalogReturnsGeneratorColumn::CrItemSk => 48, + CatalogReturnsGeneratorColumn::CrRefundedCustomerSk => 49, + CatalogReturnsGeneratorColumn::CrRefundedCdemoSk => 50, + CatalogReturnsGeneratorColumn::CrRefundedHdemoSk => 51, + CatalogReturnsGeneratorColumn::CrRefundedAddrSk => 52, + CatalogReturnsGeneratorColumn::CrReturningCustomerSk => 53, + CatalogReturnsGeneratorColumn::CrReturningCdemoSk => 54, + CatalogReturnsGeneratorColumn::CrReturningHdemoSk => 55, + CatalogReturnsGeneratorColumn::CrReturningAddrSk => 56, + CatalogReturnsGeneratorColumn::CrCallCenterSk => 57, + CatalogReturnsGeneratorColumn::CrCatalogPageSk => 58, + CatalogReturnsGeneratorColumn::CrShipModeSk => 59, + CatalogReturnsGeneratorColumn::CrWarehouseSk => 60, + CatalogReturnsGeneratorColumn::CrReasonSk => 61, + CatalogReturnsGeneratorColumn::CrOrderNumber => 62, + CatalogReturnsGeneratorColumn::CrPricingQuantity => 63, + CatalogReturnsGeneratorColumn::CrPricingNetPaid => 64, + CatalogReturnsGeneratorColumn::CrPricingExtTax => 65, + CatalogReturnsGeneratorColumn::CrPricingNetPaidIncTax => 66, + CatalogReturnsGeneratorColumn::CrPricingFee => 67, + CatalogReturnsGeneratorColumn::CrPricingExtShipCost => 68, + CatalogReturnsGeneratorColumn::CrPricingRefundedCash => 69, + CatalogReturnsGeneratorColumn::CrPricingReversedCharge => 70, + CatalogReturnsGeneratorColumn::CrPricingStoreCredit => 71, + CatalogReturnsGeneratorColumn::CrPricingNetLoss => 72, + CatalogReturnsGeneratorColumn::CrNulls => 73, + CatalogReturnsGeneratorColumn::CrPricing => 74, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + CatalogReturnsGeneratorColumn::CrReturnedDateSk => 28, + CatalogReturnsGeneratorColumn::CrReturnedTimeSk => 28, + CatalogReturnsGeneratorColumn::CrItemSk => 14, + CatalogReturnsGeneratorColumn::CrRefundedCustomerSk => 14, + CatalogReturnsGeneratorColumn::CrRefundedCdemoSk => 14, + CatalogReturnsGeneratorColumn::CrRefundedHdemoSk => 14, + CatalogReturnsGeneratorColumn::CrRefundedAddrSk => 14, + CatalogReturnsGeneratorColumn::CrReturningCustomerSk => 28, + CatalogReturnsGeneratorColumn::CrReturningCdemoSk => 14, + CatalogReturnsGeneratorColumn::CrReturningHdemoSk => 14, + CatalogReturnsGeneratorColumn::CrReturningAddrSk => 14, + CatalogReturnsGeneratorColumn::CrCallCenterSk => 0, + CatalogReturnsGeneratorColumn::CrCatalogPageSk => 14, + CatalogReturnsGeneratorColumn::CrShipModeSk => 14, + CatalogReturnsGeneratorColumn::CrWarehouseSk => 14, + CatalogReturnsGeneratorColumn::CrReasonSk => 14, + CatalogReturnsGeneratorColumn::CrOrderNumber => 0, + CatalogReturnsGeneratorColumn::CrPricingQuantity => 0, + CatalogReturnsGeneratorColumn::CrPricingNetPaid => 0, + CatalogReturnsGeneratorColumn::CrPricingExtTax => 0, + CatalogReturnsGeneratorColumn::CrPricingNetPaidIncTax => 0, + CatalogReturnsGeneratorColumn::CrPricingFee => 0, + CatalogReturnsGeneratorColumn::CrPricingExtShipCost => 0, + CatalogReturnsGeneratorColumn::CrPricingRefundedCash => 0, + CatalogReturnsGeneratorColumn::CrPricingReversedCharge => 0, + CatalogReturnsGeneratorColumn::CrPricingStoreCredit => 0, + CatalogReturnsGeneratorColumn::CrPricingNetLoss => 0, + CatalogReturnsGeneratorColumn::CrNulls => 28, + CatalogReturnsGeneratorColumn::CrPricing => 70, + } + } +} + +impl CatalogReturnsGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [CatalogReturnsGeneratorColumn] { + use CatalogReturnsGeneratorColumn::*; + &[ + CrReturnedDateSk, + CrReturnedTimeSk, + CrItemSk, + CrRefundedCustomerSk, + CrRefundedCdemoSk, + CrRefundedHdemoSk, + CrRefundedAddrSk, + CrReturningCustomerSk, + CrReturningCdemoSk, + CrReturningHdemoSk, + CrReturningAddrSk, + CrCallCenterSk, + CrCatalogPageSk, + CrShipModeSk, + CrWarehouseSk, + CrReasonSk, + CrOrderNumber, + CrPricingQuantity, + CrPricingNetPaid, + CrPricingExtTax, + CrPricingNetPaidIncTax, + CrPricingFee, + CrPricingExtShipCost, + CrPricingRefundedCash, + CrPricingReversedCharge, + CrPricingStoreCredit, + CrPricingNetLoss, + CrNulls, + CrPricing, + ] + } +} diff --git a/tpcdsgen/src/generator/catalog_sales_generator_column.rs b/tpcdsgen/src/generator/catalog_sales_generator_column.rs new file mode 100644 index 00000000..09f6a7c8 --- /dev/null +++ b/tpcdsgen/src/generator/catalog_sales_generator_column.rs @@ -0,0 +1,205 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog sales generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the catalog_sales table +/// Global column numbers 75-113 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CatalogSalesGeneratorColumn { + CsSoldDateSk, // 75 + CsSoldTimeSk, // 76 + CsShipDateSk, // 77 + CsBillCustomerSk, // 78 + CsBillCdemoSk, // 79 + CsBillHdemoSk, // 80 + CsBillAddrSk, // 81 + CsShipCustomerSk, // 82 + CsShipCdemoSk, // 83 + CsShipHdemoSk, // 84 + CsShipAddrSk, // 85 + CsCallCenterSk, // 86 + CsCatalogPageSk, // 87 + CsShipModeSk, // 88 + CsWarehouseSk, // 89 + CsSoldItemSk, // 90 + CsPromoSk, // 91 + CsOrderNumber, // 92 + CsPricingQuantity, // 93 + CsPricingWholesaleCost, // 94 + CsPricingListPrice, // 95 + CsPricingSalesPrice, // 96 + CsPricingCouponAmt, // 97 + CsPricingExtSalesPrice, // 98 + CsPricingExtDiscountAmount, // 99 + CsPricingExtWholesaleCost, // 100 + CsPricingExtListPrice, // 101 + CsPricingExtTax, // 102 + CsPricingExtShipCost, // 103 + CsPricingNetPaid, // 104 + CsPricingNetPaidIncTax, // 105 + CsPricingNetPaidIncShip, // 106 + CsPricingNetPaidIncShipTax, // 107 + CsPricingNetProfit, // 108 + CsPricing, // 109 + CsPermute, // 110 + CsNulls, // 111 + CrIsReturned, // 112 + CsPermutation, // 113 +} + +impl GeneratorColumn for CatalogSalesGeneratorColumn { + fn get_table(&self) -> Table { + Table::CatalogSales + } + + fn get_global_column_number(&self) -> i32 { + match self { + CatalogSalesGeneratorColumn::CsSoldDateSk => 75, + CatalogSalesGeneratorColumn::CsSoldTimeSk => 76, + CatalogSalesGeneratorColumn::CsShipDateSk => 77, + CatalogSalesGeneratorColumn::CsBillCustomerSk => 78, + CatalogSalesGeneratorColumn::CsBillCdemoSk => 79, + CatalogSalesGeneratorColumn::CsBillHdemoSk => 80, + CatalogSalesGeneratorColumn::CsBillAddrSk => 81, + CatalogSalesGeneratorColumn::CsShipCustomerSk => 82, + CatalogSalesGeneratorColumn::CsShipCdemoSk => 83, + CatalogSalesGeneratorColumn::CsShipHdemoSk => 84, + CatalogSalesGeneratorColumn::CsShipAddrSk => 85, + CatalogSalesGeneratorColumn::CsCallCenterSk => 86, + CatalogSalesGeneratorColumn::CsCatalogPageSk => 87, + CatalogSalesGeneratorColumn::CsShipModeSk => 88, + CatalogSalesGeneratorColumn::CsWarehouseSk => 89, + CatalogSalesGeneratorColumn::CsSoldItemSk => 90, + CatalogSalesGeneratorColumn::CsPromoSk => 91, + CatalogSalesGeneratorColumn::CsOrderNumber => 92, + CatalogSalesGeneratorColumn::CsPricingQuantity => 93, + CatalogSalesGeneratorColumn::CsPricingWholesaleCost => 94, + CatalogSalesGeneratorColumn::CsPricingListPrice => 95, + CatalogSalesGeneratorColumn::CsPricingSalesPrice => 96, + CatalogSalesGeneratorColumn::CsPricingCouponAmt => 97, + CatalogSalesGeneratorColumn::CsPricingExtSalesPrice => 98, + CatalogSalesGeneratorColumn::CsPricingExtDiscountAmount => 99, + CatalogSalesGeneratorColumn::CsPricingExtWholesaleCost => 100, + CatalogSalesGeneratorColumn::CsPricingExtListPrice => 101, + CatalogSalesGeneratorColumn::CsPricingExtTax => 102, + CatalogSalesGeneratorColumn::CsPricingExtShipCost => 103, + CatalogSalesGeneratorColumn::CsPricingNetPaid => 104, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncTax => 105, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncShip => 106, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncShipTax => 107, + CatalogSalesGeneratorColumn::CsPricingNetProfit => 108, + CatalogSalesGeneratorColumn::CsPricing => 109, + CatalogSalesGeneratorColumn::CsPermute => 110, + CatalogSalesGeneratorColumn::CsNulls => 111, + CatalogSalesGeneratorColumn::CrIsReturned => 112, + CatalogSalesGeneratorColumn::CsPermutation => 113, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + CatalogSalesGeneratorColumn::CsSoldDateSk => 1, + CatalogSalesGeneratorColumn::CsSoldTimeSk => 2, + CatalogSalesGeneratorColumn::CsShipDateSk => 14, + CatalogSalesGeneratorColumn::CsBillCustomerSk => 1, + CatalogSalesGeneratorColumn::CsBillCdemoSk => 1, + CatalogSalesGeneratorColumn::CsBillHdemoSk => 1, + CatalogSalesGeneratorColumn::CsBillAddrSk => 1, + CatalogSalesGeneratorColumn::CsShipCustomerSk => 2, + CatalogSalesGeneratorColumn::CsShipCdemoSk => 1, + CatalogSalesGeneratorColumn::CsShipHdemoSk => 1, + CatalogSalesGeneratorColumn::CsShipAddrSk => 1, + CatalogSalesGeneratorColumn::CsCallCenterSk => 1, + CatalogSalesGeneratorColumn::CsCatalogPageSk => 42, + CatalogSalesGeneratorColumn::CsShipModeSk => 14, + CatalogSalesGeneratorColumn::CsWarehouseSk => 14, + CatalogSalesGeneratorColumn::CsSoldItemSk => 1, + CatalogSalesGeneratorColumn::CsPromoSk => 14, + CatalogSalesGeneratorColumn::CsOrderNumber => 1, + CatalogSalesGeneratorColumn::CsPricingQuantity => 0, + CatalogSalesGeneratorColumn::CsPricingWholesaleCost => 0, + CatalogSalesGeneratorColumn::CsPricingListPrice => 0, + CatalogSalesGeneratorColumn::CsPricingSalesPrice => 0, + CatalogSalesGeneratorColumn::CsPricingCouponAmt => 0, + CatalogSalesGeneratorColumn::CsPricingExtSalesPrice => 0, + CatalogSalesGeneratorColumn::CsPricingExtDiscountAmount => 0, + CatalogSalesGeneratorColumn::CsPricingExtWholesaleCost => 0, + CatalogSalesGeneratorColumn::CsPricingExtListPrice => 0, + CatalogSalesGeneratorColumn::CsPricingExtTax => 0, + CatalogSalesGeneratorColumn::CsPricingExtShipCost => 0, + CatalogSalesGeneratorColumn::CsPricingNetPaid => 0, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncTax => 0, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncShip => 0, + CatalogSalesGeneratorColumn::CsPricingNetPaidIncShipTax => 0, + CatalogSalesGeneratorColumn::CsPricingNetProfit => 0, + CatalogSalesGeneratorColumn::CsPricing => 112, + CatalogSalesGeneratorColumn::CsPermute => 0, + CatalogSalesGeneratorColumn::CsNulls => 28, + CatalogSalesGeneratorColumn::CrIsReturned => 14, + CatalogSalesGeneratorColumn::CsPermutation => 0, + } + } +} + +impl CatalogSalesGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [CatalogSalesGeneratorColumn] { + use CatalogSalesGeneratorColumn::*; + &[ + CsSoldDateSk, + CsSoldTimeSk, + CsShipDateSk, + CsBillCustomerSk, + CsBillCdemoSk, + CsBillHdemoSk, + CsBillAddrSk, + CsShipCustomerSk, + CsShipCdemoSk, + CsShipHdemoSk, + CsShipAddrSk, + CsCallCenterSk, + CsCatalogPageSk, + CsShipModeSk, + CsWarehouseSk, + CsSoldItemSk, + CsPromoSk, + CsOrderNumber, + CsPricingQuantity, + CsPricingWholesaleCost, + CsPricingListPrice, + CsPricingSalesPrice, + CsPricingCouponAmt, + CsPricingExtSalesPrice, + CsPricingExtDiscountAmount, + CsPricingExtWholesaleCost, + CsPricingExtListPrice, + CsPricingExtTax, + CsPricingExtShipCost, + CsPricingNetPaid, + CsPricingNetPaidIncTax, + CsPricingNetPaidIncShip, + CsPricingNetPaidIncShipTax, + CsPricingNetProfit, + CsPricing, + CsPermute, + CsNulls, + CrIsReturned, + CsPermutation, + ] + } +} diff --git a/tpcdsgen/src/generator/customer_address_generator_column.rs b/tpcdsgen/src/generator/customer_address_generator_column.rs new file mode 100644 index 00000000..a3d754f8 --- /dev/null +++ b/tpcdsgen/src/generator/customer_address_generator_column.rs @@ -0,0 +1,115 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer address generator column definitions (CustomerAddressGeneratorColumn) + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Customer address generator column enum (CustomerAddressGeneratorColumn) +/// +/// Maps to Java CustomerAddressGeneratorColumn with global column numbers and seeds per row +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CustomerAddressGeneratorColumn { + CaAddressSk, + CaAddressId, + CaAddressStreetNum, + CaAddressStreetName, + CaAddressStreetType, + CaAddressSuiteNum, + CaAddressCity, + CaAddressCounty, + CaAddressState, + CaAddressZip, + CaAddressCountry, + CaAddressGmtOffset, + CaLocationType, + CaNulls, + CaAddress, + CaAddressStreetName2, +} + +impl CustomerAddressGeneratorColumn { + /// Get all generator column values in order + pub fn values() -> &'static [CustomerAddressGeneratorColumn] { + use CustomerAddressGeneratorColumn::*; + static VALUES: [CustomerAddressGeneratorColumn; 16] = [ + CaAddressSk, + CaAddressId, + CaAddressStreetNum, + CaAddressStreetName, + CaAddressStreetType, + CaAddressSuiteNum, + CaAddressCity, + CaAddressCounty, + CaAddressState, + CaAddressZip, + CaAddressCountry, + CaAddressGmtOffset, + CaLocationType, + CaNulls, + CaAddress, + CaAddressStreetName2, + ]; + &VALUES + } +} + +impl GeneratorColumn for CustomerAddressGeneratorColumn { + fn get_table(&self) -> Table { + Table::CustomerAddress + } + + fn get_global_column_number(&self) -> i32 { + match self { + CustomerAddressGeneratorColumn::CaAddressSk => 133, + CustomerAddressGeneratorColumn::CaAddressId => 134, + CustomerAddressGeneratorColumn::CaAddressStreetNum => 135, + CustomerAddressGeneratorColumn::CaAddressStreetName => 136, + CustomerAddressGeneratorColumn::CaAddressStreetType => 137, + CustomerAddressGeneratorColumn::CaAddressSuiteNum => 138, + CustomerAddressGeneratorColumn::CaAddressCity => 139, + CustomerAddressGeneratorColumn::CaAddressCounty => 140, + CustomerAddressGeneratorColumn::CaAddressState => 141, + CustomerAddressGeneratorColumn::CaAddressZip => 142, + CustomerAddressGeneratorColumn::CaAddressCountry => 143, + CustomerAddressGeneratorColumn::CaAddressGmtOffset => 144, + CustomerAddressGeneratorColumn::CaLocationType => 145, + CustomerAddressGeneratorColumn::CaNulls => 146, + CustomerAddressGeneratorColumn::CaAddress => 147, + CustomerAddressGeneratorColumn::CaAddressStreetName2 => 148, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + CustomerAddressGeneratorColumn::CaAddressSk => 1, + CustomerAddressGeneratorColumn::CaAddressId => 1, + CustomerAddressGeneratorColumn::CaAddressStreetNum => 1, + CustomerAddressGeneratorColumn::CaAddressStreetName => 1, + CustomerAddressGeneratorColumn::CaAddressStreetType => 1, + CustomerAddressGeneratorColumn::CaAddressSuiteNum => 1, + CustomerAddressGeneratorColumn::CaAddressCity => 1, + CustomerAddressGeneratorColumn::CaAddressCounty => 1, + CustomerAddressGeneratorColumn::CaAddressState => 1, + CustomerAddressGeneratorColumn::CaAddressZip => 1, + CustomerAddressGeneratorColumn::CaAddressCountry => 1, + CustomerAddressGeneratorColumn::CaAddressGmtOffset => 1, + CustomerAddressGeneratorColumn::CaLocationType => 1, + CustomerAddressGeneratorColumn::CaNulls => 2, + CustomerAddressGeneratorColumn::CaAddress => 7, + CustomerAddressGeneratorColumn::CaAddressStreetName2 => 1, + } + } +} diff --git a/tpcdsgen/src/generator/customer_demographics_generator_column.rs b/tpcdsgen/src/generator/customer_demographics_generator_column.rs new file mode 100644 index 00000000..c831aa54 --- /dev/null +++ b/tpcdsgen/src/generator/customer_demographics_generator_column.rs @@ -0,0 +1,73 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for CUSTOMER_DEMOGRAPHICS table (CustomerDemographicsGeneratorColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CustomerDemographicsGeneratorColumn { + CdDemoSk, + CdGender, + CdMaritalStatus, + CdEducationStatus, + CdPurchaseEstimate, + CdCreditRating, + CdDepCount, + CdDepEmployedCount, + CdDepCollegeCount, + CdNulls, +} + +impl CustomerDemographicsGeneratorColumn { + /// Get all generator columns in order (values()) + pub fn values() -> &'static [CustomerDemographicsGeneratorColumn] { + use CustomerDemographicsGeneratorColumn::*; + static VALUES: &[CustomerDemographicsGeneratorColumn] = &[ + CdDemoSk, + CdGender, + CdMaritalStatus, + CdEducationStatus, + CdPurchaseEstimate, + CdCreditRating, + CdDepCount, + CdDepEmployedCount, + CdDepCollegeCount, + CdNulls, + ]; + VALUES + } +} + +impl GeneratorColumn for CustomerDemographicsGeneratorColumn { + fn get_global_column_number(&self) -> i32 { + match self { + Self::CdDemoSk => 149, + Self::CdGender => 150, + Self::CdMaritalStatus => 151, + Self::CdEducationStatus => 152, + Self::CdPurchaseEstimate => 153, + Self::CdCreditRating => 154, + Self::CdDepCount => 155, + Self::CdDepEmployedCount => 156, + Self::CdDepCollegeCount => 157, + Self::CdNulls => 158, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + Self::CdDemoSk => 1, + Self::CdGender => 1, + Self::CdMaritalStatus => 1, + Self::CdEducationStatus => 1, + Self::CdPurchaseEstimate => 1, + Self::CdCreditRating => 1, + Self::CdDepCount => 1, + Self::CdDepEmployedCount => 1, + Self::CdDepCollegeCount => 1, + Self::CdNulls => 2, + } + } + + fn get_table(&self) -> Table { + Table::CustomerDemographics + } +} diff --git a/tpcdsgen/src/generator/customer_generator_column.rs b/tpcdsgen/src/generator/customer_generator_column.rs new file mode 100644 index 00000000..5c28d349 --- /dev/null +++ b/tpcdsgen/src/generator/customer_generator_column.rs @@ -0,0 +1,139 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer generator column definitions (CustomerGeneratorColumn) + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Customer generator column enum (CustomerGeneratorColumn) +/// +/// Each variant contains the global column number and seeds per row. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CustomerGeneratorColumn { + CCustomerSk, + CCustomerId, + CCurrentCdemoSk, + CCurrentHdemoSk, + CCurrentAddrSk, + CFirstShiptoDateId, + CFirstSalesDateId, + CSalutation, + CFirstName, + CLastName, + CPreferredCustFlag, + CBirthDay, + CBirthMonth, + CBirthYear, + CBirthCountry, + CLogin, + CEmailAddress, + CLastReviewDate, + CNulls, +} + +impl CustomerGeneratorColumn { + /// Get all column values in order + pub fn values() -> &'static [CustomerGeneratorColumn] { + use CustomerGeneratorColumn::*; + static VALUES: &[CustomerGeneratorColumn] = &[ + CCustomerSk, + CCustomerId, + CCurrentCdemoSk, + CCurrentHdemoSk, + CCurrentAddrSk, + CFirstShiptoDateId, + CFirstSalesDateId, + CSalutation, + CFirstName, + CLastName, + CPreferredCustFlag, + CBirthDay, + CBirthMonth, + CBirthYear, + CBirthCountry, + CLogin, + CEmailAddress, + CLastReviewDate, + CNulls, + ]; + VALUES + } + + /// Get the global column number for this column + fn global_column_number(&self) -> i32 { + use CustomerGeneratorColumn::*; + match self { + CCustomerSk => 114, + CCustomerId => 115, + CCurrentCdemoSk => 116, + CCurrentHdemoSk => 117, + CCurrentAddrSk => 118, + CFirstShiptoDateId => 119, + CFirstSalesDateId => 120, + CSalutation => 121, + CFirstName => 122, + CLastName => 123, + CPreferredCustFlag => 124, + CBirthDay => 125, + CBirthMonth => 126, + CBirthYear => 127, + CBirthCountry => 128, + CLogin => 129, + CEmailAddress => 130, + CLastReviewDate => 131, + CNulls => 132, + } + } + + /// Get the seeds per row for this column + fn seeds_per_row(&self) -> i32 { + use CustomerGeneratorColumn::*; + match self { + CCustomerSk => 1, + CCustomerId => 1, + CCurrentCdemoSk => 1, + CCurrentHdemoSk => 1, + CCurrentAddrSk => 1, + CFirstShiptoDateId => 0, + CFirstSalesDateId => 1, + CSalutation => 1, + CFirstName => 1, + CLastName => 1, + CPreferredCustFlag => 2, + CBirthDay => 1, + CBirthMonth => 0, + CBirthYear => 0, + CBirthCountry => 1, + CLogin => 1, + CEmailAddress => 23, + CLastReviewDate => 1, + CNulls => 2, + } + } +} + +impl GeneratorColumn for CustomerGeneratorColumn { + fn get_table(&self) -> Table { + Table::Customer + } + + fn get_global_column_number(&self) -> i32 { + self.global_column_number() + } + + fn get_seeds_per_row(&self) -> i32 { + self.seeds_per_row() + } +} diff --git a/tpcdsgen/src/generator/date_dim_generator_column.rs b/tpcdsgen/src/generator/date_dim_generator_column.rs new file mode 100644 index 00000000..52000987 --- /dev/null +++ b/tpcdsgen/src/generator/date_dim_generator_column.rs @@ -0,0 +1,123 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for DATE_DIM table (DateDimGeneratorColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DateDimGeneratorColumn { + DDateSk, + DDateId, + DDate, + DMonthSeq, + DWeekSeq, + DQuarterSeq, + DYear, + DDow, + DMoy, + DDom, + DQoy, + DFyYear, + DFyQuarterSeq, + DFyWeekSeq, + DDayName, + DQuarterName, + DHoliday, + DWeekend, + DFollowingHoliday, + DFirstDom, + DLastDom, + DSameDayLy, + DSameDayLq, + DCurrentDay, + DCurrentWeek, + DCurrentMonth, + DCurrentQuarter, + DCurrentYear, + DNulls, +} + +impl DateDimGeneratorColumn { + pub fn values() -> &'static [DateDimGeneratorColumn] { + use DateDimGeneratorColumn::*; + static VALUES: &[DateDimGeneratorColumn] = &[ + DDateSk, + DDateId, + DDate, + DMonthSeq, + DWeekSeq, + DQuarterSeq, + DYear, + DDow, + DMoy, + DDom, + DQoy, + DFyYear, + DFyQuarterSeq, + DFyWeekSeq, + DDayName, + DQuarterName, + DHoliday, + DWeekend, + DFollowingHoliday, + DFirstDom, + DLastDom, + DSameDayLy, + DSameDayLq, + DCurrentDay, + DCurrentWeek, + DCurrentMonth, + DCurrentQuarter, + DCurrentYear, + DNulls, + ]; + VALUES + } + + fn get_column_info(&self) -> (i32, i32) { + use DateDimGeneratorColumn::*; + match self { + DDateSk => (159, 0), + DDateId => (160, 0), + DDate => (161, 0), + DMonthSeq => (162, 0), + DWeekSeq => (163, 0), + DQuarterSeq => (164, 0), + DYear => (165, 0), + DDow => (166, 0), + DMoy => (167, 0), + DDom => (168, 0), + DQoy => (169, 0), + DFyYear => (170, 0), + DFyQuarterSeq => (171, 0), + DFyWeekSeq => (172, 0), + DDayName => (173, 0), + DQuarterName => (174, 0), + DHoliday => (175, 0), + DWeekend => (176, 0), + DFollowingHoliday => (177, 0), + DFirstDom => (178, 0), + DLastDom => (179, 0), + DSameDayLy => (180, 0), + DSameDayLq => (181, 0), + DCurrentDay => (182, 0), + DCurrentWeek => (183, 0), + DCurrentMonth => (184, 0), + DCurrentQuarter => (185, 0), + DCurrentYear => (186, 0), + DNulls => (187, 2), + } + } +} + +impl GeneratorColumn for DateDimGeneratorColumn { + fn get_table(&self) -> Table { + Table::DateDim + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/dbgen_version_generator_column.rs b/tpcdsgen/src/generator/dbgen_version_generator_column.rs new file mode 100644 index 00000000..e57e9117 --- /dev/null +++ b/tpcdsgen/src/generator/dbgen_version_generator_column.rs @@ -0,0 +1,61 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// DbgenVersion generator columns (DbgenVersionGeneratorColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DbgenVersionGeneratorColumn { + DvVersion, + DvCreateDate, + DvCreateTime, + DvCmdlineArgs, +} + +impl DbgenVersionGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [DbgenVersionGeneratorColumn] { + use DbgenVersionGeneratorColumn::*; + static VALUES: &[DbgenVersionGeneratorColumn] = + &[DvVersion, DvCreateDate, DvCreateTime, DvCmdlineArgs]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + /// Values exactly match Java implementation + fn get_column_info(&self) -> (i32, i32) { + use DbgenVersionGeneratorColumn::*; + match self { + DvVersion => (476, 1), + DvCreateDate => (477, 1), + DvCreateTime => (478, 1), + DvCmdlineArgs => (479, 1), + } + } +} + +impl GeneratorColumn for DbgenVersionGeneratorColumn { + fn get_table(&self) -> Table { + Table::DbgenVersion + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/generator_column.rs b/tpcdsgen/src/generator/generator_column.rs new file mode 100644 index 00000000..55190e5c --- /dev/null +++ b/tpcdsgen/src/generator/generator_column.rs @@ -0,0 +1,57 @@ +use crate::column::Table; + +/// GeneratorColumns are columns that are used only within the context of the +/// generator logic. The Enums that implement this interface may include columns +/// that are not user visible and will sometimes omit columns that are user visible +/// (because those get derived from other columns). +/// +/// GeneratorColumn +pub trait GeneratorColumn: Send + Sync { + /// Get the table this generator column belongs to + fn get_table(&self) -> Table; + + /// Get the global column number for this generator column + fn get_global_column_number(&self) -> i32; + + /// Get the number of seeds per row for this generator column + fn get_seeds_per_row(&self) -> i32; +} + +#[cfg(test)] +mod tests { + use super::*; + + // Create a simple test implementation + struct TestGeneratorColumn { + table: Table, + global_column_number: i32, + seeds_per_row: i32, + } + + impl GeneratorColumn for TestGeneratorColumn { + fn get_table(&self) -> Table { + self.table + } + + fn get_global_column_number(&self) -> i32 { + self.global_column_number + } + + fn get_seeds_per_row(&self) -> i32 { + self.seeds_per_row + } + } + + #[test] + fn test_generator_column_trait() { + let test_column = TestGeneratorColumn { + table: Table::CallCenter, + global_column_number: 5, + seeds_per_row: 10, + }; + + assert_eq!(test_column.get_table(), Table::CallCenter); + assert_eq!(test_column.get_global_column_number(), 5); + assert_eq!(test_column.get_seeds_per_row(), 10); + } +} diff --git a/tpcdsgen/src/generator/household_demographics_generator_column.rs b/tpcdsgen/src/generator/household_demographics_generator_column.rs new file mode 100644 index 00000000..ebcd790a --- /dev/null +++ b/tpcdsgen/src/generator/household_demographics_generator_column.rs @@ -0,0 +1,71 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for HOUSEHOLD_DEMOGRAPHICS table (HouseholdDemographicsGeneratorColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HouseholdDemographicsGeneratorColumn { + HdDemoSk, + HdIncomeBandId, + HdBuyPotential, + HdDepCount, + HdVehicleCount, + HdNulls, +} + +impl HouseholdDemographicsGeneratorColumn { + /// Get all generator columns in order (values()) + pub fn values() -> &'static [HouseholdDemographicsGeneratorColumn] { + use HouseholdDemographicsGeneratorColumn::*; + static VALUES: &[HouseholdDemographicsGeneratorColumn] = &[ + HdDemoSk, + HdIncomeBandId, + HdBuyPotential, + HdDepCount, + HdVehicleCount, + HdNulls, + ]; + VALUES + } +} + +impl GeneratorColumn for HouseholdDemographicsGeneratorColumn { + fn get_global_column_number(&self) -> i32 { + match self { + Self::HdDemoSk => 188, + Self::HdIncomeBandId => 189, + Self::HdBuyPotential => 190, + Self::HdDepCount => 191, + Self::HdVehicleCount => 192, + Self::HdNulls => 193, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + Self::HdDemoSk => 1, + Self::HdIncomeBandId => 1, + Self::HdBuyPotential => 1, + Self::HdDepCount => 1, + Self::HdVehicleCount => 1, + Self::HdNulls => 2, + } + } + + fn get_table(&self) -> Table { + Table::HouseholdDemographics + } +} diff --git a/tpcdsgen/src/generator/income_band_generator_column.rs b/tpcdsgen/src/generator/income_band_generator_column.rs new file mode 100644 index 00000000..10e1ee0e --- /dev/null +++ b/tpcdsgen/src/generator/income_band_generator_column.rs @@ -0,0 +1,47 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Income band generator columns (IncomeBandGeneratorColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum IncomeBandGeneratorColumn { + IbIncomeBandId, + IbLowerBound, + IbUpperBound, + IbNulls, +} + +impl IncomeBandGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [IncomeBandGeneratorColumn] { + use IncomeBandGeneratorColumn::*; + static VALUES: &[IncomeBandGeneratorColumn] = + &[IbIncomeBandId, IbLowerBound, IbUpperBound, IbNulls]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + /// Values exactly match Java implementation + fn get_column_info(&self) -> (i32, i32) { + use IncomeBandGeneratorColumn::*; + match self { + IbIncomeBandId => (194, 1), + IbLowerBound => (195, 1), + IbUpperBound => (196, 1), + IbNulls => (197, 2), + } + } +} + +impl GeneratorColumn for IncomeBandGeneratorColumn { + fn get_table(&self) -> Table { + Table::IncomeBand + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/inventory_generator_column.rs b/tpcdsgen/src/generator/inventory_generator_column.rs new file mode 100644 index 00000000..3e9a5468 --- /dev/null +++ b/tpcdsgen/src/generator/inventory_generator_column.rs @@ -0,0 +1,70 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Inventory generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for the inventory table. +/// These map to InventoryGeneratorColumn.java +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InventoryGeneratorColumn { + InvDateSk, + InvItemSk, + InvWarehouseSk, + InvQuantityOnHand, + InvNulls, +} + +impl InventoryGeneratorColumn { + /// Get all variants in order + pub fn all_variants() -> &'static [InventoryGeneratorColumn] { + use InventoryGeneratorColumn::*; + static VARIANTS: [InventoryGeneratorColumn; 5] = [ + InvDateSk, + InvItemSk, + InvWarehouseSk, + InvQuantityOnHand, + InvNulls, + ]; + &VARIANTS + } +} + +impl GeneratorColumn for InventoryGeneratorColumn { + fn get_table(&self) -> Table { + Table::Inventory + } + + fn get_global_column_number(&self) -> i32 { + match self { + InventoryGeneratorColumn::InvDateSk => 198, + InventoryGeneratorColumn::InvItemSk => 199, + InventoryGeneratorColumn::InvWarehouseSk => 200, + InventoryGeneratorColumn::InvQuantityOnHand => 201, + InventoryGeneratorColumn::InvNulls => 202, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + InventoryGeneratorColumn::InvDateSk => 1, + InventoryGeneratorColumn::InvItemSk => 1, + InventoryGeneratorColumn::InvWarehouseSk => 1, + InventoryGeneratorColumn::InvQuantityOnHand => 1, + InventoryGeneratorColumn::InvNulls => 2, + } + } +} diff --git a/tpcdsgen/src/generator/item_generator_column.rs b/tpcdsgen/src/generator/item_generator_column.rs new file mode 100644 index 00000000..d2d45ac2 --- /dev/null +++ b/tpcdsgen/src/generator/item_generator_column.rs @@ -0,0 +1,150 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Item generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for item table +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ItemGeneratorColumn { + IItemSk, + IItemId, + IRecStartDateId, + IRecEndDateId, + IItemDesc, + ICurrentPrice, + IWholesaleCost, + IBrandId, + IBrand, + IClassId, + IClass, + ICategoryId, + ICategory, + IManufactId, + IManufact, + ISize, + IFormulation, + IColor, + IUnits, + IContainer, + IManagerId, + IProductName, + INulls, + IScd, + IPromoSk, +} + +impl GeneratorColumn for ItemGeneratorColumn { + fn get_table(&self) -> Table { + Table::Item + } + + fn get_global_column_number(&self) -> i32 { + use ItemGeneratorColumn::*; + match self { + IItemSk => 203, + IItemId => 204, + IRecStartDateId => 205, + IRecEndDateId => 206, + IItemDesc => 207, + ICurrentPrice => 208, + IWholesaleCost => 209, + IBrandId => 210, + IBrand => 211, + IClassId => 212, + IClass => 213, + ICategoryId => 214, + ICategory => 215, + IManufactId => 216, + IManufact => 217, + ISize => 218, + IFormulation => 219, + IColor => 220, + IUnits => 221, + IContainer => 222, + IManagerId => 223, + IProductName => 224, + INulls => 225, + IScd => 226, + IPromoSk => 227, + } + } + + fn get_seeds_per_row(&self) -> i32 { + use ItemGeneratorColumn::*; + match self { + IItemSk => 1, + IItemId => 1, + IRecStartDateId => 1, + IRecEndDateId => 2, + IItemDesc => 200, + ICurrentPrice => 2, + IWholesaleCost => 1, + IBrandId => 1, + IBrand => 1, + IClassId => 1, + IClass => 1, + ICategoryId => 1, + ICategory => 1, + IManufactId => 2, + IManufact => 1, + ISize => 1, + IFormulation => 50, + IColor => 1, + IUnits => 1, + IContainer => 1, + IManagerId => 2, + IProductName => 1, + INulls => 2, + IScd => 1, + IPromoSk => 2, + } + } +} + +impl ItemGeneratorColumn { + /// Get all generator columns in order + pub fn all_columns() -> &'static [ItemGeneratorColumn] { + use ItemGeneratorColumn::*; + &[ + IItemSk, + IItemId, + IRecStartDateId, + IRecEndDateId, + IItemDesc, + ICurrentPrice, + IWholesaleCost, + IBrandId, + IBrand, + IClassId, + IClass, + ICategoryId, + ICategory, + IManufactId, + IManufact, + ISize, + IFormulation, + IColor, + IUnits, + IContainer, + IManagerId, + IProductName, + INulls, + IScd, + IPromoSk, + ] + } +} diff --git a/tpcdsgen/src/generator/mod.rs b/tpcdsgen/src/generator/mod.rs new file mode 100644 index 00000000..e4d4d342 --- /dev/null +++ b/tpcdsgen/src/generator/mod.rs @@ -0,0 +1,53 @@ +pub mod call_center_generator_column; +pub mod catalog_page_generator_column; +pub mod catalog_returns_generator_column; +pub mod catalog_sales_generator_column; +pub mod customer_address_generator_column; +pub mod customer_demographics_generator_column; +pub mod customer_generator_column; +pub mod date_dim_generator_column; +pub mod dbgen_version_generator_column; +pub mod generator_column; +pub mod household_demographics_generator_column; +pub mod income_band_generator_column; +pub mod inventory_generator_column; +pub mod item_generator_column; +pub mod promotion_generator_column; +pub mod reason_generator_column; +pub mod ship_mode_generator_column; +pub mod store_generator_column; +pub mod store_returns_generator_column; +pub mod store_sales_generator_column; +pub mod time_dim_generator_column; +pub mod warehouse_generator_column; +pub mod web_page_generator_column; +pub mod web_returns_generator_column; +pub mod web_sales_generator_column; +pub mod web_site_generator_column; + +pub use call_center_generator_column::CallCenterGeneratorColumn; +pub use catalog_page_generator_column::CatalogPageGeneratorColumn; +pub use catalog_returns_generator_column::CatalogReturnsGeneratorColumn; +pub use catalog_sales_generator_column::CatalogSalesGeneratorColumn; +pub use customer_address_generator_column::CustomerAddressGeneratorColumn; +pub use customer_demographics_generator_column::CustomerDemographicsGeneratorColumn; +pub use customer_generator_column::CustomerGeneratorColumn; +pub use date_dim_generator_column::DateDimGeneratorColumn; +pub use dbgen_version_generator_column::DbgenVersionGeneratorColumn; +pub use generator_column::GeneratorColumn; +pub use household_demographics_generator_column::HouseholdDemographicsGeneratorColumn; +pub use income_band_generator_column::IncomeBandGeneratorColumn; +pub use inventory_generator_column::InventoryGeneratorColumn; +pub use item_generator_column::ItemGeneratorColumn; +pub use promotion_generator_column::PromotionGeneratorColumn; +pub use reason_generator_column::ReasonGeneratorColumn; +pub use ship_mode_generator_column::ShipModeGeneratorColumn; +pub use store_generator_column::StoreGeneratorColumn; +pub use store_returns_generator_column::StoreReturnsGeneratorColumn; +pub use store_sales_generator_column::StoreSalesGeneratorColumn; +pub use time_dim_generator_column::TimeDimGeneratorColumn; +pub use warehouse_generator_column::WarehouseGeneratorColumn; +pub use web_page_generator_column::WebPageGeneratorColumn; +pub use web_returns_generator_column::WebReturnsGeneratorColumn; +pub use web_sales_generator_column::WebSalesGeneratorColumn; +pub use web_site_generator_column::WebSiteGeneratorColumn; diff --git a/tpcdsgen/src/generator/promotion_generator_column.rs b/tpcdsgen/src/generator/promotion_generator_column.rs new file mode 100644 index 00000000..04202581 --- /dev/null +++ b/tpcdsgen/src/generator/promotion_generator_column.rs @@ -0,0 +1,146 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PromotionGeneratorColumn { + PPromoSk, + PPromoId, + PStartDateId, + PEndDateId, + PItemSk, + PCost, + PResponseTarget, + PPromoName, + PChannelDmail, + PChannelEmail, + PChannelCatalog, + PChannelTv, + PChannelRadio, + PChannelPress, + PChannelEvent, + PChannelDemo, + PChannelDetails, + PPurpose, + PDiscountActive, + PNulls, +} + +impl PromotionGeneratorColumn { + pub fn values() -> &'static [PromotionGeneratorColumn] { + &[ + PromotionGeneratorColumn::PPromoSk, + PromotionGeneratorColumn::PPromoId, + PromotionGeneratorColumn::PStartDateId, + PromotionGeneratorColumn::PEndDateId, + PromotionGeneratorColumn::PItemSk, + PromotionGeneratorColumn::PCost, + PromotionGeneratorColumn::PResponseTarget, + PromotionGeneratorColumn::PPromoName, + PromotionGeneratorColumn::PChannelDmail, + PromotionGeneratorColumn::PChannelEmail, + PromotionGeneratorColumn::PChannelCatalog, + PromotionGeneratorColumn::PChannelTv, + PromotionGeneratorColumn::PChannelRadio, + PromotionGeneratorColumn::PChannelPress, + PromotionGeneratorColumn::PChannelEvent, + PromotionGeneratorColumn::PChannelDemo, + PromotionGeneratorColumn::PChannelDetails, + PromotionGeneratorColumn::PPurpose, + PromotionGeneratorColumn::PDiscountActive, + PromotionGeneratorColumn::PNulls, + ] + } +} + +impl GeneratorColumn for PromotionGeneratorColumn { + fn get_table(&self) -> Table { + Table::Promotion + } + + fn get_global_column_number(&self) -> i32 { + match self { + PromotionGeneratorColumn::PPromoSk => 228, + PromotionGeneratorColumn::PPromoId => 229, + PromotionGeneratorColumn::PStartDateId => 230, + PromotionGeneratorColumn::PEndDateId => 231, + PromotionGeneratorColumn::PItemSk => 232, + PromotionGeneratorColumn::PCost => 233, + PromotionGeneratorColumn::PResponseTarget => 234, + PromotionGeneratorColumn::PPromoName => 235, + PromotionGeneratorColumn::PChannelDmail => 236, + PromotionGeneratorColumn::PChannelEmail => 237, + PromotionGeneratorColumn::PChannelCatalog => 238, + PromotionGeneratorColumn::PChannelTv => 239, + PromotionGeneratorColumn::PChannelRadio => 240, + PromotionGeneratorColumn::PChannelPress => 241, + PromotionGeneratorColumn::PChannelEvent => 242, + PromotionGeneratorColumn::PChannelDemo => 243, + PromotionGeneratorColumn::PChannelDetails => 244, + PromotionGeneratorColumn::PPurpose => 245, + PromotionGeneratorColumn::PDiscountActive => 246, + PromotionGeneratorColumn::PNulls => 247, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + PromotionGeneratorColumn::PChannelDetails => 100, + PromotionGeneratorColumn::PNulls => 2, + _ => 1, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_promotion_generator_column_count() { + assert_eq!(PromotionGeneratorColumn::values().len(), 20); + } + + #[test] + fn test_promotion_generator_global_column_numbers() { + assert_eq!( + PromotionGeneratorColumn::PPromoSk.get_global_column_number(), + 228 + ); + assert_eq!( + PromotionGeneratorColumn::PNulls.get_global_column_number(), + 247 + ); + } + + #[test] + fn test_promotion_generator_seeds_per_row() { + assert_eq!(PromotionGeneratorColumn::PPromoSk.get_seeds_per_row(), 1); + assert_eq!( + PromotionGeneratorColumn::PChannelDetails.get_seeds_per_row(), + 100 + ); + assert_eq!(PromotionGeneratorColumn::PNulls.get_seeds_per_row(), 2); + } + + #[test] + fn test_promotion_generator_table() { + assert_eq!( + PromotionGeneratorColumn::PPromoSk.get_table(), + Table::Promotion + ); + } +} diff --git a/tpcdsgen/src/generator/reason_generator_column.rs b/tpcdsgen/src/generator/reason_generator_column.rs new file mode 100644 index 00000000..55889ad5 --- /dev/null +++ b/tpcdsgen/src/generator/reason_generator_column.rs @@ -0,0 +1,47 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Reason generator columns (ReasonGeneratorColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReasonGeneratorColumn { + RReasonSk, + RReasonId, + RReasonDescription, + RNulls, +} + +impl ReasonGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [ReasonGeneratorColumn] { + use ReasonGeneratorColumn::*; + static VALUES: &[ReasonGeneratorColumn] = + &[RReasonSk, RReasonId, RReasonDescription, RNulls]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + /// Values exactly match Java implementation + fn get_column_info(&self) -> (i32, i32) { + use ReasonGeneratorColumn::*; + match self { + RReasonSk => (248, 1), + RReasonId => (249, 1), + RReasonDescription => (250, 1), + RNulls => (251, 2), + } + } +} + +impl GeneratorColumn for ReasonGeneratorColumn { + fn get_table(&self) -> Table { + Table::Reason + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/ship_mode_generator_column.rs b/tpcdsgen/src/generator/ship_mode_generator_column.rs new file mode 100644 index 00000000..c6027b7e --- /dev/null +++ b/tpcdsgen/src/generator/ship_mode_generator_column.rs @@ -0,0 +1,60 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Ship mode generator columns (ShipModeGeneratorColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ShipModeGeneratorColumn { + SmShipModeSk, + SmShipModeId, + SmType, + SmCode, + SmContract, + SmCarrier, + SmNulls, +} + +impl ShipModeGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [ShipModeGeneratorColumn] { + use ShipModeGeneratorColumn::*; + static VALUES: &[ShipModeGeneratorColumn] = &[ + SmShipModeSk, + SmShipModeId, + SmType, + SmCode, + SmContract, + SmCarrier, + SmNulls, + ]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + /// Values exactly match Java implementation + fn get_column_info(&self) -> (i32, i32) { + use ShipModeGeneratorColumn::*; + match self { + SmShipModeSk => (252, 1), + SmShipModeId => (253, 1), + SmType => (254, 1), + SmCode => (255, 1), + SmContract => (256, 21), + SmCarrier => (257, 1), + SmNulls => (258, 2), + } + } +} + +impl GeneratorColumn for ShipModeGeneratorColumn { + fn get_table(&self) -> Table { + Table::ShipMode + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/store_generator_column.rs b/tpcdsgen/src/generator/store_generator_column.rs new file mode 100644 index 00000000..5ebbc8a2 --- /dev/null +++ b/tpcdsgen/src/generator/store_generator_column.rs @@ -0,0 +1,182 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for store table +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StoreGeneratorColumn { + WStoreSk, + WStoreId, + WStoreRecStartDateId, + WStoreRecEndDateId, + WStoreClosedDateId, + WStoreName, + WStoreEmployees, + WStoreFloorSpace, + WStoreHours, + WStoreManager, + WStoreMarketId, + WStoreTaxPercentage, + WStoreGeographyClass, + WStoreMarketDesc, + WStoreMarketManager, + WStoreDivisionId, + WStoreDivisionName, + WStoreCompanyId, + WStoreCompanyName, + WStoreAddressStreetNum, + WStoreAddressStreetName1, + WStoreAddressStreetType, + WStoreAddressSuiteNum, + WStoreAddressCity, + WStoreAddressCounty, + WStoreAddressState, + WStoreAddressZip, + WStoreAddressCountry, + WStoreAddressGmtOffset, + WStoreNulls, + WStoreType, + WStoreScd, + WStoreAddress, +} + +impl GeneratorColumn for StoreGeneratorColumn { + fn get_table(&self) -> Table { + Table::Store + } + + fn get_global_column_number(&self) -> i32 { + use StoreGeneratorColumn::*; + match self { + WStoreSk => 259, + WStoreId => 260, + WStoreRecStartDateId => 261, + WStoreRecEndDateId => 262, + WStoreClosedDateId => 263, + WStoreName => 264, + WStoreEmployees => 265, + WStoreFloorSpace => 266, + WStoreHours => 267, + WStoreManager => 268, + WStoreMarketId => 269, + WStoreTaxPercentage => 270, + WStoreGeographyClass => 271, + WStoreMarketDesc => 272, + WStoreMarketManager => 273, + WStoreDivisionId => 274, + WStoreDivisionName => 275, + WStoreCompanyId => 276, + WStoreCompanyName => 277, + WStoreAddressStreetNum => 278, + WStoreAddressStreetName1 => 279, + WStoreAddressStreetType => 280, + WStoreAddressSuiteNum => 281, + WStoreAddressCity => 282, + WStoreAddressCounty => 283, + WStoreAddressState => 284, + WStoreAddressZip => 285, + WStoreAddressCountry => 286, + WStoreAddressGmtOffset => 287, + WStoreNulls => 288, + WStoreType => 289, + WStoreScd => 290, + WStoreAddress => 291, + } + } + + fn get_seeds_per_row(&self) -> i32 { + use StoreGeneratorColumn::*; + match self { + WStoreSk => 1, + WStoreId => 1, + WStoreRecStartDateId => 1, + WStoreRecEndDateId => 2, + WStoreClosedDateId => 2, + WStoreName => 0, + WStoreEmployees => 1, + WStoreFloorSpace => 1, + WStoreHours => 1, + WStoreManager => 2, + WStoreMarketId => 1, + WStoreTaxPercentage => 1, + WStoreGeographyClass => 1, + WStoreMarketDesc => 100, + WStoreMarketManager => 2, + WStoreDivisionId => 1, + WStoreDivisionName => 1, + WStoreCompanyId => 1, + WStoreCompanyName => 1, + WStoreAddressStreetNum => 1, + WStoreAddressStreetName1 => 1, + WStoreAddressStreetType => 1, + WStoreAddressSuiteNum => 1, + WStoreAddressCity => 1, + WStoreAddressCounty => 1, + WStoreAddressState => 1, + WStoreAddressZip => 1, + WStoreAddressCountry => 1, + WStoreAddressGmtOffset => 1, + WStoreNulls => 2, + WStoreType => 1, + WStoreScd => 1, + WStoreAddress => 7, + } + } +} + +impl StoreGeneratorColumn { + /// Get all generator columns in order + pub fn all_columns() -> &'static [StoreGeneratorColumn] { + use StoreGeneratorColumn::*; + &[ + WStoreSk, + WStoreId, + WStoreRecStartDateId, + WStoreRecEndDateId, + WStoreClosedDateId, + WStoreName, + WStoreEmployees, + WStoreFloorSpace, + WStoreHours, + WStoreManager, + WStoreMarketId, + WStoreTaxPercentage, + WStoreGeographyClass, + WStoreMarketDesc, + WStoreMarketManager, + WStoreDivisionId, + WStoreDivisionName, + WStoreCompanyId, + WStoreCompanyName, + WStoreAddressStreetNum, + WStoreAddressStreetName1, + WStoreAddressStreetType, + WStoreAddressSuiteNum, + WStoreAddressCity, + WStoreAddressCounty, + WStoreAddressState, + WStoreAddressZip, + WStoreAddressCountry, + WStoreAddressGmtOffset, + WStoreNulls, + WStoreType, + WStoreScd, + WStoreAddress, + ] + } +} diff --git a/tpcdsgen/src/generator/store_returns_generator_column.rs b/tpcdsgen/src/generator/store_returns_generator_column.rs new file mode 100644 index 00000000..693820e4 --- /dev/null +++ b/tpcdsgen/src/generator/store_returns_generator_column.rs @@ -0,0 +1,137 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store returns generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the store_returns table +/// Global column numbers 292-313 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StoreReturnsGeneratorColumn { + SrReturnedDateSk, // 292 + SrReturnedTimeSk, // 293 + SrItemSk, // 294 + SrCustomerSk, // 295 + SrCdemoSk, // 296 + SrHdemoSk, // 297 + SrAddrSk, // 298 + SrStoreSk, // 299 + SrReasonSk, // 300 + SrTicketNumber, // 301 + SrPricingQuantity, // 302 + SrPricingNetPaid, // 303 + SrPricingExtTax, // 304 + SrPricingNetPaidIncTax, // 305 + SrPricingFee, // 306 + SrPricingExtShipCost, // 307 + SrPricingRefundedCash, // 308 + SrPricingReversedCharge, // 309 + SrPricingStoreCredit, // 310 + SrPricingNetLoss, // 311 + SrPricing, // 312 + SrNulls, // 313 +} + +impl GeneratorColumn for StoreReturnsGeneratorColumn { + fn get_table(&self) -> Table { + Table::StoreReturns + } + + fn get_global_column_number(&self) -> i32 { + match self { + StoreReturnsGeneratorColumn::SrReturnedDateSk => 292, + StoreReturnsGeneratorColumn::SrReturnedTimeSk => 293, + StoreReturnsGeneratorColumn::SrItemSk => 294, + StoreReturnsGeneratorColumn::SrCustomerSk => 295, + StoreReturnsGeneratorColumn::SrCdemoSk => 296, + StoreReturnsGeneratorColumn::SrHdemoSk => 297, + StoreReturnsGeneratorColumn::SrAddrSk => 298, + StoreReturnsGeneratorColumn::SrStoreSk => 299, + StoreReturnsGeneratorColumn::SrReasonSk => 300, + StoreReturnsGeneratorColumn::SrTicketNumber => 301, + StoreReturnsGeneratorColumn::SrPricingQuantity => 302, + StoreReturnsGeneratorColumn::SrPricingNetPaid => 303, + StoreReturnsGeneratorColumn::SrPricingExtTax => 304, + StoreReturnsGeneratorColumn::SrPricingNetPaidIncTax => 305, + StoreReturnsGeneratorColumn::SrPricingFee => 306, + StoreReturnsGeneratorColumn::SrPricingExtShipCost => 307, + StoreReturnsGeneratorColumn::SrPricingRefundedCash => 308, + StoreReturnsGeneratorColumn::SrPricingReversedCharge => 309, + StoreReturnsGeneratorColumn::SrPricingStoreCredit => 310, + StoreReturnsGeneratorColumn::SrPricingNetLoss => 311, + StoreReturnsGeneratorColumn::SrPricing => 312, + StoreReturnsGeneratorColumn::SrNulls => 313, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + StoreReturnsGeneratorColumn::SrReturnedDateSk => 32, + StoreReturnsGeneratorColumn::SrReturnedTimeSk => 32, + StoreReturnsGeneratorColumn::SrItemSk => 16, + StoreReturnsGeneratorColumn::SrCustomerSk => 16, + StoreReturnsGeneratorColumn::SrCdemoSk => 16, + StoreReturnsGeneratorColumn::SrHdemoSk => 16, + StoreReturnsGeneratorColumn::SrAddrSk => 16, + StoreReturnsGeneratorColumn::SrStoreSk => 16, + StoreReturnsGeneratorColumn::SrReasonSk => 16, + StoreReturnsGeneratorColumn::SrTicketNumber => 16, + StoreReturnsGeneratorColumn::SrPricingQuantity => 0, + StoreReturnsGeneratorColumn::SrPricingNetPaid => 0, + StoreReturnsGeneratorColumn::SrPricingExtTax => 0, + StoreReturnsGeneratorColumn::SrPricingNetPaidIncTax => 0, + StoreReturnsGeneratorColumn::SrPricingFee => 0, + StoreReturnsGeneratorColumn::SrPricingExtShipCost => 0, + StoreReturnsGeneratorColumn::SrPricingRefundedCash => 0, + StoreReturnsGeneratorColumn::SrPricingReversedCharge => 0, + StoreReturnsGeneratorColumn::SrPricingStoreCredit => 0, + StoreReturnsGeneratorColumn::SrPricingNetLoss => 0, + StoreReturnsGeneratorColumn::SrPricing => 80, + StoreReturnsGeneratorColumn::SrNulls => 32, + } + } +} + +impl StoreReturnsGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [StoreReturnsGeneratorColumn] { + use StoreReturnsGeneratorColumn::*; + &[ + SrReturnedDateSk, + SrReturnedTimeSk, + SrItemSk, + SrCustomerSk, + SrCdemoSk, + SrHdemoSk, + SrAddrSk, + SrStoreSk, + SrReasonSk, + SrTicketNumber, + SrPricingQuantity, + SrPricingNetPaid, + SrPricingExtTax, + SrPricingNetPaidIncTax, + SrPricingFee, + SrPricingExtShipCost, + SrPricingRefundedCash, + SrPricingReversedCharge, + SrPricingStoreCredit, + SrPricingNetLoss, + SrPricing, + SrNulls, + ] + } +} diff --git a/tpcdsgen/src/generator/store_sales_generator_column.rs b/tpcdsgen/src/generator/store_sales_generator_column.rs new file mode 100644 index 00000000..788c5750 --- /dev/null +++ b/tpcdsgen/src/generator/store_sales_generator_column.rs @@ -0,0 +1,153 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store sales generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the store_sales table +/// Global column numbers 314-339 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StoreSalesGeneratorColumn { + SsSoldDateSk, // 314 + SsSoldTimeSk, // 315 + SsSoldItemSk, // 316 + SsSoldCustomerSk, // 317 + SsSoldCdemoSk, // 318 + SsSoldHdemoSk, // 319 + SsSoldAddrSk, // 320 + SsSoldStoreSk, // 321 + SsSoldPromoSk, // 322 + SsTicketNumber, // 323 + SsPricingQuantity, // 324 + SsPricingWholesaleCost, // 325 + SsPricingListPrice, // 326 + SsPricingSalesPrice, // 327 + SsPricingCouponAmt, // 328 + SsPricingExtSalesPrice, // 329 + SsPricingExtWholesaleCost, // 330 + SsPricingExtListPrice, // 331 + SsPricingExtTax, // 332 + SsPricingNetPaid, // 333 + SsPricingNetPaidIncTax, // 334 + SsPricingNetProfit, // 335 + SrIsReturned, // 336 + SsPricing, // 337 + SsNulls, // 338 + SsPermutation, // 339 +} + +impl GeneratorColumn for StoreSalesGeneratorColumn { + fn get_table(&self) -> Table { + Table::StoreSales + } + + fn get_global_column_number(&self) -> i32 { + match self { + StoreSalesGeneratorColumn::SsSoldDateSk => 314, + StoreSalesGeneratorColumn::SsSoldTimeSk => 315, + StoreSalesGeneratorColumn::SsSoldItemSk => 316, + StoreSalesGeneratorColumn::SsSoldCustomerSk => 317, + StoreSalesGeneratorColumn::SsSoldCdemoSk => 318, + StoreSalesGeneratorColumn::SsSoldHdemoSk => 319, + StoreSalesGeneratorColumn::SsSoldAddrSk => 320, + StoreSalesGeneratorColumn::SsSoldStoreSk => 321, + StoreSalesGeneratorColumn::SsSoldPromoSk => 322, + StoreSalesGeneratorColumn::SsTicketNumber => 323, + StoreSalesGeneratorColumn::SsPricingQuantity => 324, + StoreSalesGeneratorColumn::SsPricingWholesaleCost => 325, + StoreSalesGeneratorColumn::SsPricingListPrice => 326, + StoreSalesGeneratorColumn::SsPricingSalesPrice => 327, + StoreSalesGeneratorColumn::SsPricingCouponAmt => 328, + StoreSalesGeneratorColumn::SsPricingExtSalesPrice => 329, + StoreSalesGeneratorColumn::SsPricingExtWholesaleCost => 330, + StoreSalesGeneratorColumn::SsPricingExtListPrice => 331, + StoreSalesGeneratorColumn::SsPricingExtTax => 332, + StoreSalesGeneratorColumn::SsPricingNetPaid => 333, + StoreSalesGeneratorColumn::SsPricingNetPaidIncTax => 334, + StoreSalesGeneratorColumn::SsPricingNetProfit => 335, + StoreSalesGeneratorColumn::SrIsReturned => 336, + StoreSalesGeneratorColumn::SsPricing => 337, + StoreSalesGeneratorColumn::SsNulls => 338, + StoreSalesGeneratorColumn::SsPermutation => 339, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + StoreSalesGeneratorColumn::SsSoldDateSk => 2, + StoreSalesGeneratorColumn::SsSoldTimeSk => 2, + StoreSalesGeneratorColumn::SsSoldItemSk => 1, + StoreSalesGeneratorColumn::SsSoldCustomerSk => 1, + StoreSalesGeneratorColumn::SsSoldCdemoSk => 1, + StoreSalesGeneratorColumn::SsSoldHdemoSk => 1, + StoreSalesGeneratorColumn::SsSoldAddrSk => 1, + StoreSalesGeneratorColumn::SsSoldStoreSk => 1, + StoreSalesGeneratorColumn::SsSoldPromoSk => 16, + StoreSalesGeneratorColumn::SsTicketNumber => 1, + StoreSalesGeneratorColumn::SsPricingQuantity => 1, + StoreSalesGeneratorColumn::SsPricingWholesaleCost => 0, + StoreSalesGeneratorColumn::SsPricingListPrice => 0, + StoreSalesGeneratorColumn::SsPricingSalesPrice => 0, + StoreSalesGeneratorColumn::SsPricingCouponAmt => 0, + StoreSalesGeneratorColumn::SsPricingExtSalesPrice => 0, + StoreSalesGeneratorColumn::SsPricingExtWholesaleCost => 0, + StoreSalesGeneratorColumn::SsPricingExtListPrice => 0, + StoreSalesGeneratorColumn::SsPricingExtTax => 0, + StoreSalesGeneratorColumn::SsPricingNetPaid => 0, + StoreSalesGeneratorColumn::SsPricingNetPaidIncTax => 0, + StoreSalesGeneratorColumn::SsPricingNetProfit => 0, + StoreSalesGeneratorColumn::SrIsReturned => 16, + StoreSalesGeneratorColumn::SsPricing => 128, + StoreSalesGeneratorColumn::SsNulls => 32, + StoreSalesGeneratorColumn::SsPermutation => 0, + } + } +} + +impl StoreSalesGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [StoreSalesGeneratorColumn] { + use StoreSalesGeneratorColumn::*; + &[ + SsSoldDateSk, + SsSoldTimeSk, + SsSoldItemSk, + SsSoldCustomerSk, + SsSoldCdemoSk, + SsSoldHdemoSk, + SsSoldAddrSk, + SsSoldStoreSk, + SsSoldPromoSk, + SsTicketNumber, + SsPricingQuantity, + SsPricingWholesaleCost, + SsPricingListPrice, + SsPricingSalesPrice, + SsPricingCouponAmt, + SsPricingExtSalesPrice, + SsPricingExtWholesaleCost, + SsPricingExtListPrice, + SsPricingExtTax, + SsPricingNetPaid, + SsPricingNetPaidIncTax, + SsPricingNetProfit, + SrIsReturned, + SsPricing, + SsNulls, + SsPermutation, + ] + } +} diff --git a/tpcdsgen/src/generator/time_dim_generator_column.rs b/tpcdsgen/src/generator/time_dim_generator_column.rs new file mode 100644 index 00000000..a9130b3f --- /dev/null +++ b/tpcdsgen/src/generator/time_dim_generator_column.rs @@ -0,0 +1,60 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for TIME_DIM table (TimeDimGeneratorColumn) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TimeDimGeneratorColumn { + TTimeSk, + TTimeId, + TTime, + THour, + TMinute, + TSecond, + TAmPm, + TShift, + TSubShift, + TMealTime, + TNulls, +} + +impl TimeDimGeneratorColumn { + pub fn values() -> &'static [TimeDimGeneratorColumn] { + use TimeDimGeneratorColumn::*; + static VALUES: &[TimeDimGeneratorColumn] = &[ + TTimeSk, TTimeId, TTime, THour, TMinute, TSecond, TAmPm, TShift, TSubShift, TMealTime, + TNulls, + ]; + VALUES + } + + fn get_column_info(&self) -> (i32, i32) { + use TimeDimGeneratorColumn::*; + match self { + TTimeSk => (340, 1), + TTimeId => (341, 1), + TTime => (342, 1), + THour => (343, 1), + TMinute => (344, 1), + TSecond => (345, 1), + TAmPm => (346, 1), + TShift => (347, 1), + TSubShift => (348, 1), + TMealTime => (349, 1), + TNulls => (350, 1), + } + } +} + +impl GeneratorColumn for TimeDimGeneratorColumn { + fn get_table(&self) -> Table { + Table::TimeDim + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/warehouse_generator_column.rs b/tpcdsgen/src/generator/warehouse_generator_column.rs new file mode 100644 index 00000000..0f2fa38f --- /dev/null +++ b/tpcdsgen/src/generator/warehouse_generator_column.rs @@ -0,0 +1,86 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Warehouse generator columns (WarehouseGeneratorColumn enum) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WarehouseGeneratorColumn { + WWarehouseSk, + WWarehouseId, + WWarehouseName, + WWarehouseSqFt, + WAddressStreetNum, + WAddressStreetName1, + WAddressStreetType, + WAddressSuiteNum, + WAddressCity, + WAddressCounty, + WAddressState, + WAddressZip, + WAddressCountry, + WAddressGmtOffset, + WNulls, + WWarehouseAddress, +} + +impl WarehouseGeneratorColumn { + /// Get all generator columns in order + pub fn values() -> &'static [WarehouseGeneratorColumn] { + use WarehouseGeneratorColumn::*; + static VALUES: &[WarehouseGeneratorColumn] = &[ + WWarehouseSk, + WWarehouseId, + WWarehouseName, + WWarehouseSqFt, + WAddressStreetNum, + WAddressStreetName1, + WAddressStreetType, + WAddressSuiteNum, + WAddressCity, + WAddressCounty, + WAddressState, + WAddressZip, + WAddressCountry, + WAddressGmtOffset, + WNulls, + WWarehouseAddress, + ]; + VALUES + } + + /// Get the global column number and seeds per row for this generator column + fn get_column_info(&self) -> (i32, i32) { + use WarehouseGeneratorColumn::*; + match self { + WWarehouseSk => (351, 1), + WWarehouseId => (352, 1), + WWarehouseName => (353, 80), + WWarehouseSqFt => (354, 1), + WAddressStreetNum => (355, 1), + WAddressStreetName1 => (356, 1), + WAddressStreetType => (357, 1), + WAddressSuiteNum => (358, 1), + WAddressCity => (359, 1), + WAddressCounty => (360, 1), + WAddressState => (361, 1), + WAddressZip => (362, 1), + WAddressCountry => (363, 1), + WAddressGmtOffset => (364, 1), + WNulls => (365, 2), + WWarehouseAddress => (366, 7), + } + } +} + +impl GeneratorColumn for WarehouseGeneratorColumn { + fn get_table(&self) -> Table { + Table::Warehouse + } + + fn get_global_column_number(&self) -> i32 { + self.get_column_info().0 + } + + fn get_seeds_per_row(&self) -> i32 { + self.get_column_info().1 + } +} diff --git a/tpcdsgen/src/generator/web_page_generator_column.rs b/tpcdsgen/src/generator/web_page_generator_column.rs new file mode 100644 index 00000000..d4f95e6c --- /dev/null +++ b/tpcdsgen/src/generator/web_page_generator_column.rs @@ -0,0 +1,83 @@ +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Generator columns for the WEB_PAGE table (WebPageGeneratorColumn) +/// Maps to the Java enum with the same name +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WebPageGeneratorColumn { + WpPageSk, + WpPageId, + WpRecStartDateId, + WpRecEndDateId, + WpCreationDateSk, + WpAccessDateSk, + WpAutogenFlag, + WpCustomerSk, + WpUrl, + WpType, + WpCharCount, + WpLinkCount, + WpImageCount, + WpMaxAdCount, + WpNulls, + WpScd, +} + +impl GeneratorColumn for WebPageGeneratorColumn { + fn get_table(&self) -> Table { + Table::WebPage + } + + fn get_global_column_number(&self) -> i32 { + match self { + Self::WpPageSk => 367, + Self::WpPageId => 368, + Self::WpRecStartDateId => 369, + Self::WpRecEndDateId => 370, + Self::WpCreationDateSk => 371, + Self::WpAccessDateSk => 372, + Self::WpAutogenFlag => 373, + Self::WpCustomerSk => 374, + Self::WpUrl => 375, + Self::WpType => 376, + Self::WpCharCount => 377, + Self::WpLinkCount => 378, + Self::WpImageCount => 379, + Self::WpMaxAdCount => 380, + Self::WpNulls => 381, + Self::WpScd => 382, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + Self::WpCreationDateSk => 2, + Self::WpNulls => 2, + _ => 1, + } + } +} + +impl WebPageGeneratorColumn { + /// Get all generator column values (for Table integration) + pub fn values() -> Vec { + vec![ + Self::WpPageSk, + Self::WpPageId, + Self::WpRecStartDateId, + Self::WpRecEndDateId, + Self::WpCreationDateSk, + Self::WpAccessDateSk, + Self::WpAutogenFlag, + Self::WpCustomerSk, + Self::WpUrl, + Self::WpType, + Self::WpCharCount, + Self::WpLinkCount, + Self::WpImageCount, + Self::WpMaxAdCount, + Self::WpNulls, + Self::WpScd, + ] + } +} diff --git a/tpcdsgen/src/generator/web_returns_generator_column.rs b/tpcdsgen/src/generator/web_returns_generator_column.rs new file mode 100644 index 00000000..b9926a30 --- /dev/null +++ b/tpcdsgen/src/generator/web_returns_generator_column.rs @@ -0,0 +1,153 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web returns generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the web_returns table +/// Global column numbers 383-408 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WebReturnsGeneratorColumn { + WrReturnedDateSk, // 383 + WrReturnedTimeSk, // 384 + WrItemSk, // 385 + WrRefundedCustomerSk, // 386 + WrRefundedCdemoSk, // 387 + WrRefundedHdemoSk, // 388 + WrRefundedAddrSk, // 389 + WrReturningCustomerSk, // 390 + WrReturningCdemoSk, // 391 + WrReturningHdemoSk, // 392 + WrReturningAddrSk, // 393 + WrWebPageSk, // 394 + WrReasonSk, // 395 + WrOrderNumber, // 396 + WrPricingQuantity, // 397 + WrPricingNetPaid, // 398 + WrPricingExtTax, // 399 + WrPricingNetPaidIncTax, // 400 + WrPricingFee, // 401 + WrPricingExtShipCost, // 402 + WrPricingRefundedCash, // 403 + WrPricingReversedCharge, // 404 + WrPricingStoreCredit, // 405 + WrPricingNetLoss, // 406 + WrPricing, // 407 + WrNulls, // 408 +} + +impl GeneratorColumn for WebReturnsGeneratorColumn { + fn get_table(&self) -> Table { + Table::WebReturns + } + + fn get_global_column_number(&self) -> i32 { + match self { + WebReturnsGeneratorColumn::WrReturnedDateSk => 383, + WebReturnsGeneratorColumn::WrReturnedTimeSk => 384, + WebReturnsGeneratorColumn::WrItemSk => 385, + WebReturnsGeneratorColumn::WrRefundedCustomerSk => 386, + WebReturnsGeneratorColumn::WrRefundedCdemoSk => 387, + WebReturnsGeneratorColumn::WrRefundedHdemoSk => 388, + WebReturnsGeneratorColumn::WrRefundedAddrSk => 389, + WebReturnsGeneratorColumn::WrReturningCustomerSk => 390, + WebReturnsGeneratorColumn::WrReturningCdemoSk => 391, + WebReturnsGeneratorColumn::WrReturningHdemoSk => 392, + WebReturnsGeneratorColumn::WrReturningAddrSk => 393, + WebReturnsGeneratorColumn::WrWebPageSk => 394, + WebReturnsGeneratorColumn::WrReasonSk => 395, + WebReturnsGeneratorColumn::WrOrderNumber => 396, + WebReturnsGeneratorColumn::WrPricingQuantity => 397, + WebReturnsGeneratorColumn::WrPricingNetPaid => 398, + WebReturnsGeneratorColumn::WrPricingExtTax => 399, + WebReturnsGeneratorColumn::WrPricingNetPaidIncTax => 400, + WebReturnsGeneratorColumn::WrPricingFee => 401, + WebReturnsGeneratorColumn::WrPricingExtShipCost => 402, + WebReturnsGeneratorColumn::WrPricingRefundedCash => 403, + WebReturnsGeneratorColumn::WrPricingReversedCharge => 404, + WebReturnsGeneratorColumn::WrPricingStoreCredit => 405, + WebReturnsGeneratorColumn::WrPricingNetLoss => 406, + WebReturnsGeneratorColumn::WrPricing => 407, + WebReturnsGeneratorColumn::WrNulls => 408, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + WebReturnsGeneratorColumn::WrReturnedDateSk => 32, + WebReturnsGeneratorColumn::WrReturnedTimeSk => 32, + WebReturnsGeneratorColumn::WrItemSk => 16, + WebReturnsGeneratorColumn::WrRefundedCustomerSk => 16, + WebReturnsGeneratorColumn::WrRefundedCdemoSk => 16, + WebReturnsGeneratorColumn::WrRefundedHdemoSk => 16, + WebReturnsGeneratorColumn::WrRefundedAddrSk => 16, + WebReturnsGeneratorColumn::WrReturningCustomerSk => 16, + WebReturnsGeneratorColumn::WrReturningCdemoSk => 16, + WebReturnsGeneratorColumn::WrReturningHdemoSk => 16, + WebReturnsGeneratorColumn::WrReturningAddrSk => 16, + WebReturnsGeneratorColumn::WrWebPageSk => 16, + WebReturnsGeneratorColumn::WrReasonSk => 16, + WebReturnsGeneratorColumn::WrOrderNumber => 0, + WebReturnsGeneratorColumn::WrPricingQuantity => 0, + WebReturnsGeneratorColumn::WrPricingNetPaid => 0, + WebReturnsGeneratorColumn::WrPricingExtTax => 0, + WebReturnsGeneratorColumn::WrPricingNetPaidIncTax => 0, + WebReturnsGeneratorColumn::WrPricingFee => 0, + WebReturnsGeneratorColumn::WrPricingExtShipCost => 0, + WebReturnsGeneratorColumn::WrPricingRefundedCash => 0, + WebReturnsGeneratorColumn::WrPricingReversedCharge => 0, + WebReturnsGeneratorColumn::WrPricingStoreCredit => 0, + WebReturnsGeneratorColumn::WrPricingNetLoss => 0, + WebReturnsGeneratorColumn::WrPricing => 80, + WebReturnsGeneratorColumn::WrNulls => 32, + } + } +} + +impl WebReturnsGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [WebReturnsGeneratorColumn] { + use WebReturnsGeneratorColumn::*; + &[ + WrReturnedDateSk, + WrReturnedTimeSk, + WrItemSk, + WrRefundedCustomerSk, + WrRefundedCdemoSk, + WrRefundedHdemoSk, + WrRefundedAddrSk, + WrReturningCustomerSk, + WrReturningCdemoSk, + WrReturningHdemoSk, + WrReturningAddrSk, + WrWebPageSk, + WrReasonSk, + WrOrderNumber, + WrPricingQuantity, + WrPricingNetPaid, + WrPricingExtTax, + WrPricingNetPaidIncTax, + WrPricingFee, + WrPricingExtShipCost, + WrPricingRefundedCash, + WrPricingReversedCharge, + WrPricingStoreCredit, + WrPricingNetLoss, + WrPricing, + WrNulls, + ] + } +} diff --git a/tpcdsgen/src/generator/web_sales_generator_column.rs b/tpcdsgen/src/generator/web_sales_generator_column.rs new file mode 100644 index 00000000..a973f98e --- /dev/null +++ b/tpcdsgen/src/generator/web_sales_generator_column.rs @@ -0,0 +1,201 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web sales generator column definitions + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +/// Enum representing all generator columns for the web_sales table +/// Global column numbers 409-446 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WebSalesGeneratorColumn { + WsSoldDateSk, // 409 + WsSoldTimeSk, // 410 + WsShipDateSk, // 411 + WsItemSk, // 412 + WsBillCustomerSk, // 413 + WsBillCdemoSk, // 414 + WsBillHdemoSk, // 415 + WsBillAddrSk, // 416 + WsShipCustomerSk, // 417 + WsShipCdemoSk, // 418 + WsShipHdemoSk, // 419 + WsShipAddrSk, // 420 + WsWebPageSk, // 421 + WsWebSiteSk, // 422 + WsShipModeSk, // 423 + WsWarehouseSk, // 424 + WsPromoSk, // 425 + WsOrderNumber, // 426 + WsPricingQuantity, // 427 + WsPricingWholesaleCost, // 428 + WsPricingListPrice, // 429 + WsPricingSalesPrice, // 430 + WsPricingExtDiscountAmt, // 431 + WsPricingExtSalesPrice, // 432 + WsPricingExtWholesaleCost, // 433 + WsPricingExtListPrice, // 434 + WsPricingExtTax, // 435 + WsPricingCouponAmt, // 436 + WsPricingExtShipCost, // 437 + WsPricingNetPaid, // 438 + WsPricingNetPaidIncTax, // 439 + WsPricingNetPaidIncShip, // 440 + WsPricingNetPaidIncShipTax, // 441 + WsPricingNetProfit, // 442 + WsPricing, // 443 + WsNulls, // 444 + WrIsReturned, // 445 + WsPermutation, // 446 +} + +impl GeneratorColumn for WebSalesGeneratorColumn { + fn get_table(&self) -> Table { + Table::WebSales + } + + fn get_global_column_number(&self) -> i32 { + match self { + WebSalesGeneratorColumn::WsSoldDateSk => 409, + WebSalesGeneratorColumn::WsSoldTimeSk => 410, + WebSalesGeneratorColumn::WsShipDateSk => 411, + WebSalesGeneratorColumn::WsItemSk => 412, + WebSalesGeneratorColumn::WsBillCustomerSk => 413, + WebSalesGeneratorColumn::WsBillCdemoSk => 414, + WebSalesGeneratorColumn::WsBillHdemoSk => 415, + WebSalesGeneratorColumn::WsBillAddrSk => 416, + WebSalesGeneratorColumn::WsShipCustomerSk => 417, + WebSalesGeneratorColumn::WsShipCdemoSk => 418, + WebSalesGeneratorColumn::WsShipHdemoSk => 419, + WebSalesGeneratorColumn::WsShipAddrSk => 420, + WebSalesGeneratorColumn::WsWebPageSk => 421, + WebSalesGeneratorColumn::WsWebSiteSk => 422, + WebSalesGeneratorColumn::WsShipModeSk => 423, + WebSalesGeneratorColumn::WsWarehouseSk => 424, + WebSalesGeneratorColumn::WsPromoSk => 425, + WebSalesGeneratorColumn::WsOrderNumber => 426, + WebSalesGeneratorColumn::WsPricingQuantity => 427, + WebSalesGeneratorColumn::WsPricingWholesaleCost => 428, + WebSalesGeneratorColumn::WsPricingListPrice => 429, + WebSalesGeneratorColumn::WsPricingSalesPrice => 430, + WebSalesGeneratorColumn::WsPricingExtDiscountAmt => 431, + WebSalesGeneratorColumn::WsPricingExtSalesPrice => 432, + WebSalesGeneratorColumn::WsPricingExtWholesaleCost => 433, + WebSalesGeneratorColumn::WsPricingExtListPrice => 434, + WebSalesGeneratorColumn::WsPricingExtTax => 435, + WebSalesGeneratorColumn::WsPricingCouponAmt => 436, + WebSalesGeneratorColumn::WsPricingExtShipCost => 437, + WebSalesGeneratorColumn::WsPricingNetPaid => 438, + WebSalesGeneratorColumn::WsPricingNetPaidIncTax => 439, + WebSalesGeneratorColumn::WsPricingNetPaidIncShip => 440, + WebSalesGeneratorColumn::WsPricingNetPaidIncShipTax => 441, + WebSalesGeneratorColumn::WsPricingNetProfit => 442, + WebSalesGeneratorColumn::WsPricing => 443, + WebSalesGeneratorColumn::WsNulls => 444, + WebSalesGeneratorColumn::WrIsReturned => 445, + WebSalesGeneratorColumn::WsPermutation => 446, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + WebSalesGeneratorColumn::WsSoldDateSk => 2, + WebSalesGeneratorColumn::WsSoldTimeSk => 2, + WebSalesGeneratorColumn::WsShipDateSk => 16, + WebSalesGeneratorColumn::WsItemSk => 1, + WebSalesGeneratorColumn::WsBillCustomerSk => 1, + WebSalesGeneratorColumn::WsBillCdemoSk => 1, + WebSalesGeneratorColumn::WsBillHdemoSk => 1, + WebSalesGeneratorColumn::WsBillAddrSk => 1, + WebSalesGeneratorColumn::WsShipCustomerSk => 2, + WebSalesGeneratorColumn::WsShipCdemoSk => 2, + WebSalesGeneratorColumn::WsShipHdemoSk => 1, + WebSalesGeneratorColumn::WsShipAddrSk => 1, + WebSalesGeneratorColumn::WsWebPageSk => 16, + WebSalesGeneratorColumn::WsWebSiteSk => 16, + WebSalesGeneratorColumn::WsShipModeSk => 16, + WebSalesGeneratorColumn::WsWarehouseSk => 16, + WebSalesGeneratorColumn::WsPromoSk => 16, + WebSalesGeneratorColumn::WsOrderNumber => 1, + WebSalesGeneratorColumn::WsPricingQuantity => 1, + WebSalesGeneratorColumn::WsPricingWholesaleCost => 1, + WebSalesGeneratorColumn::WsPricingListPrice => 0, + WebSalesGeneratorColumn::WsPricingSalesPrice => 0, + WebSalesGeneratorColumn::WsPricingExtDiscountAmt => 0, + WebSalesGeneratorColumn::WsPricingExtSalesPrice => 0, + WebSalesGeneratorColumn::WsPricingExtWholesaleCost => 0, + WebSalesGeneratorColumn::WsPricingExtListPrice => 0, + WebSalesGeneratorColumn::WsPricingExtTax => 0, + WebSalesGeneratorColumn::WsPricingCouponAmt => 0, + WebSalesGeneratorColumn::WsPricingExtShipCost => 0, + WebSalesGeneratorColumn::WsPricingNetPaid => 0, + WebSalesGeneratorColumn::WsPricingNetPaidIncTax => 0, + WebSalesGeneratorColumn::WsPricingNetPaidIncShip => 0, + WebSalesGeneratorColumn::WsPricingNetPaidIncShipTax => 0, + WebSalesGeneratorColumn::WsPricingNetProfit => 0, + WebSalesGeneratorColumn::WsPricing => 128, + WebSalesGeneratorColumn::WsNulls => 32, + WebSalesGeneratorColumn::WrIsReturned => 16, + WebSalesGeneratorColumn::WsPermutation => 0, + } + } +} + +impl WebSalesGeneratorColumn { + /// Returns all variants in order + pub fn all_variants() -> &'static [WebSalesGeneratorColumn] { + use WebSalesGeneratorColumn::*; + &[ + WsSoldDateSk, + WsSoldTimeSk, + WsShipDateSk, + WsItemSk, + WsBillCustomerSk, + WsBillCdemoSk, + WsBillHdemoSk, + WsBillAddrSk, + WsShipCustomerSk, + WsShipCdemoSk, + WsShipHdemoSk, + WsShipAddrSk, + WsWebPageSk, + WsWebSiteSk, + WsShipModeSk, + WsWarehouseSk, + WsPromoSk, + WsOrderNumber, + WsPricingQuantity, + WsPricingWholesaleCost, + WsPricingListPrice, + WsPricingSalesPrice, + WsPricingExtDiscountAmt, + WsPricingExtSalesPrice, + WsPricingExtWholesaleCost, + WsPricingExtListPrice, + WsPricingExtTax, + WsPricingCouponAmt, + WsPricingExtShipCost, + WsPricingNetPaid, + WsPricingNetPaidIncTax, + WsPricingNetPaidIncShip, + WsPricingNetPaidIncShipTax, + WsPricingNetProfit, + WsPricing, + WsNulls, + WrIsReturned, + WsPermutation, + ] + } +} diff --git a/tpcdsgen/src/generator/web_site_generator_column.rs b/tpcdsgen/src/generator/web_site_generator_column.rs new file mode 100644 index 00000000..f6c85d3c --- /dev/null +++ b/tpcdsgen/src/generator/web_site_generator_column.rs @@ -0,0 +1,176 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::column::Table; +use crate::generator::GeneratorColumn; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum WebSiteGeneratorColumn { + WebSiteSk, + WebSiteId, + WebRecStartDateId, + WebRecEndDateId, + WebName, + WebOpenDate, + WebCloseDate, + WebClass, + WebManager, + WebMarketId, + WebMarketClass, + WebMarketDesc, + WebMarketManager, + WebCompanyId, + WebCompanyName, + WebAddressStreetNum, + WebAddressStreetName1, + WebAddressStreetType, + WebAddressSuiteNum, + WebAddressCity, + WebAddressCounty, + WebAddressState, + WebAddressZip, + WebAddressCountry, + WebAddressGmtOffset, + WebTaxPercentage, + WebNulls, + WebAddress, + WebScd, +} + +impl WebSiteGeneratorColumn { + pub fn values() -> &'static [WebSiteGeneratorColumn] { + &[ + WebSiteGeneratorColumn::WebSiteSk, + WebSiteGeneratorColumn::WebSiteId, + WebSiteGeneratorColumn::WebRecStartDateId, + WebSiteGeneratorColumn::WebRecEndDateId, + WebSiteGeneratorColumn::WebName, + WebSiteGeneratorColumn::WebOpenDate, + WebSiteGeneratorColumn::WebCloseDate, + WebSiteGeneratorColumn::WebClass, + WebSiteGeneratorColumn::WebManager, + WebSiteGeneratorColumn::WebMarketId, + WebSiteGeneratorColumn::WebMarketClass, + WebSiteGeneratorColumn::WebMarketDesc, + WebSiteGeneratorColumn::WebMarketManager, + WebSiteGeneratorColumn::WebCompanyId, + WebSiteGeneratorColumn::WebCompanyName, + WebSiteGeneratorColumn::WebAddressStreetNum, + WebSiteGeneratorColumn::WebAddressStreetName1, + WebSiteGeneratorColumn::WebAddressStreetType, + WebSiteGeneratorColumn::WebAddressSuiteNum, + WebSiteGeneratorColumn::WebAddressCity, + WebSiteGeneratorColumn::WebAddressCounty, + WebSiteGeneratorColumn::WebAddressState, + WebSiteGeneratorColumn::WebAddressZip, + WebSiteGeneratorColumn::WebAddressCountry, + WebSiteGeneratorColumn::WebAddressGmtOffset, + WebSiteGeneratorColumn::WebTaxPercentage, + WebSiteGeneratorColumn::WebNulls, + WebSiteGeneratorColumn::WebAddress, + WebSiteGeneratorColumn::WebScd, + ] + } +} + +impl GeneratorColumn for WebSiteGeneratorColumn { + fn get_table(&self) -> Table { + Table::WebSite + } + + fn get_global_column_number(&self) -> i32 { + match self { + WebSiteGeneratorColumn::WebSiteSk => 447, + WebSiteGeneratorColumn::WebSiteId => 448, + WebSiteGeneratorColumn::WebRecStartDateId => 449, + WebSiteGeneratorColumn::WebRecEndDateId => 450, + WebSiteGeneratorColumn::WebName => 451, + WebSiteGeneratorColumn::WebOpenDate => 452, + WebSiteGeneratorColumn::WebCloseDate => 453, + WebSiteGeneratorColumn::WebClass => 454, + WebSiteGeneratorColumn::WebManager => 455, + WebSiteGeneratorColumn::WebMarketId => 456, + WebSiteGeneratorColumn::WebMarketClass => 457, + WebSiteGeneratorColumn::WebMarketDesc => 458, + WebSiteGeneratorColumn::WebMarketManager => 459, + WebSiteGeneratorColumn::WebCompanyId => 460, + WebSiteGeneratorColumn::WebCompanyName => 461, + WebSiteGeneratorColumn::WebAddressStreetNum => 462, + WebSiteGeneratorColumn::WebAddressStreetName1 => 463, + WebSiteGeneratorColumn::WebAddressStreetType => 464, + WebSiteGeneratorColumn::WebAddressSuiteNum => 465, + WebSiteGeneratorColumn::WebAddressCity => 466, + WebSiteGeneratorColumn::WebAddressCounty => 467, + WebSiteGeneratorColumn::WebAddressState => 468, + WebSiteGeneratorColumn::WebAddressZip => 469, + WebSiteGeneratorColumn::WebAddressCountry => 470, + WebSiteGeneratorColumn::WebAddressGmtOffset => 471, + WebSiteGeneratorColumn::WebTaxPercentage => 472, + WebSiteGeneratorColumn::WebNulls => 473, + WebSiteGeneratorColumn::WebAddress => 474, + WebSiteGeneratorColumn::WebScd => 475, + } + } + + fn get_seeds_per_row(&self) -> i32 { + match self { + WebSiteGeneratorColumn::WebManager => 2, + WebSiteGeneratorColumn::WebMarketClass => 20, + WebSiteGeneratorColumn::WebMarketDesc => 100, + WebSiteGeneratorColumn::WebMarketManager => 2, + WebSiteGeneratorColumn::WebNulls => 2, + WebSiteGeneratorColumn::WebAddress => 7, + WebSiteGeneratorColumn::WebScd => 70, + _ => 1, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_web_site_generator_column_count() { + assert_eq!(WebSiteGeneratorColumn::values().len(), 29); + } + + #[test] + fn test_web_site_generator_column_numbers() { + assert_eq!( + WebSiteGeneratorColumn::WebSiteSk.get_global_column_number(), + 447 + ); + assert_eq!( + WebSiteGeneratorColumn::WebScd.get_global_column_number(), + 475 + ); + } + + #[test] + fn test_web_site_generator_seeds_per_row() { + assert_eq!(WebSiteGeneratorColumn::WebSiteSk.get_seeds_per_row(), 1); + assert_eq!(WebSiteGeneratorColumn::WebManager.get_seeds_per_row(), 2); + assert_eq!( + WebSiteGeneratorColumn::WebMarketClass.get_seeds_per_row(), + 20 + ); + assert_eq!( + WebSiteGeneratorColumn::WebMarketDesc.get_seeds_per_row(), + 100 + ); + assert_eq!(WebSiteGeneratorColumn::WebAddress.get_seeds_per_row(), 7); + assert_eq!(WebSiteGeneratorColumn::WebScd.get_seeds_per_row(), 70); + } +} diff --git a/tpcdsgen/src/join_key_utils.rs b/tpcdsgen/src/join_key_utils.rs new file mode 100644 index 00000000..cabe091e --- /dev/null +++ b/tpcdsgen/src/join_key_utils.rs @@ -0,0 +1,453 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Join key generation utilities for TPC-DS foreign key relationships. +//! +//! This module provides functionality to generate foreign keys (join keys) between +//! TPC-DS tables, respecting the benchmark's referential integrity requirements. +use crate::config::{Scaling, Table}; +use crate::distribution::calendar_distribution::{CalendarDistribution, CalendarWeights}; +use crate::distribution::catalog_page_distributions::CatalogPageTypesDistribution; +use crate::distribution::hours_distribution::{HoursDistribution, HoursWeights}; +use crate::error::{Result, TpcdsError}; +use crate::generator::GeneratorColumn; +use crate::pseudo_table_scaling_infos::PseudoTableScalingInfos; +use crate::random::{RandomNumberStream, RandomValueGenerator}; +use crate::slowly_changing_dimension_utils; +use crate::types::Date; + +#[allow(dead_code)] +const WEB_PAGES_PER_SITE: i32 = 123; +#[allow(dead_code)] +const WEB_DATE_STAGGER: i64 = 17; +const CS_MIN_SHIP_DELAY: i32 = 2; +const CS_MAX_SHIP_DELAY: i32 = 90; +const CATALOGS_PER_YEAR: i32 = 18; + +/// Generates a join key (foreign key) from one table/column to another table. +/// +/// This is the main entry point for generating foreign keys between TPC-DS tables. +/// It routes to specialized key generators based on the target table type. +/// +/// # Arguments +/// +/// * `from_column` - The column generating the join key +/// * `random_number_stream` - Random number stream for generation +/// * `to_table` - The target table being referenced +/// * `join_count` - Context-dependent value (often a date or row number) +/// * `scaling` - Scaling information for the dataset +/// +/// # Returns +/// +/// The generated join key value, or -1 if no valid key can be generated +pub fn generate_join_key( + from_column: &dyn GeneratorColumn, + random_number_stream: &mut dyn RandomNumberStream, + to_table: Table, + join_count: i64, + scaling: &Scaling, +) -> Result { + // NOTE: to_table is config::Table (for CLI), from_column.get_table() returns + // column::Table (now same as table::Table). Different enums for different purposes. + + match to_table { + Table::CatalogPage => { + generate_catalog_page_join_key(random_number_stream, join_count, scaling) + } + Table::DateDim => { + let year = RandomValueGenerator::generate_uniform_random_int( + Date::DATE_MINIMUM.year(), + Date::DATE_MAXIMUM.year(), + random_number_stream, + ); + generate_date_join_key(random_number_stream, from_column, join_count, year, scaling) + } + Table::TimeDim => generate_time_join_key(from_column, random_number_stream), + _ => { + if to_table.keeps_history() { + generate_scd_join_key(to_table, random_number_stream, join_count, scaling) + } else { + Ok(RandomValueGenerator::generate_uniform_random_key( + 1, + scaling.get_row_count(to_table), + random_number_stream, + )) + } + } + } +} + +/// Generates a join key to the catalog_page table. +/// +/// Calculates which catalog page based on the date and catalog type (monthly, bi-annual, quarterly). +/// Each catalog type has a different frequency within the year. +/// +/// Based on JoinKeyUtils.java:generateCatalogPageJoinKey +fn generate_catalog_page_join_key( + random_number_stream: &mut dyn RandomNumberStream, + julian_date: i64, + scaling: &Scaling, +) -> Result { + let pages_per_catalog = ((scaling.get_row_count(Table::CatalogPage) / CATALOGS_PER_YEAR as i64) + / (Date::DATE_MAXIMUM.year() - Date::DATE_MINIMUM.year() + 2) as i64) + as i32; + + let catalog_type = + CatalogPageTypesDistribution::pick_random_catalog_page_type(random_number_stream)?; + let page = RandomValueGenerator::generate_uniform_random_int( + 1, + pages_per_catalog, + random_number_stream, + ); + + let offset_from_start = (julian_date - Date::JULIAN_DATA_START_DATE - 1) as i32; + let mut count = (offset_from_start / 365) * CATALOGS_PER_YEAR; + let offset = offset_from_start % 365; + + // Adjust count based on catalog type frequency + match catalog_type.as_str() { + "bi-annual" => { + if offset > 183 { + count += 1; + } + } + "quarterly" => { + count += offset / 91; + } + "monthly" => { + count += offset / 31; + } + _ => { + return Err(TpcdsError::new(&format!( + "Invalid catalog_page_type: {}", + catalog_type + ))); + } + } + + Ok((count * pages_per_catalog + page) as i64) +} + +/// Generates a join key to the date_dim table. +/// +/// Different table types use different date selection strategies: +/// - Sales tables use SALES or SALES_LEAP_YEAR weights +/// - Returns tables use date returns logic (with lag from sale date) +/// - Web-related tables use web join key logic +/// - Other tables use UNIFORM or UNIFORM_LEAP_YEAR weights +/// +/// Based on JoinKeyUtils.java:generateDateJoinKey (lines 109-142) +fn generate_date_join_key( + random_number_stream: &mut dyn RandomNumberStream, + from_column: &dyn GeneratorColumn, + join_count: i64, + year: i32, + scaling: &Scaling, +) -> Result { + use crate::column::Table as ColumnTable; + + let from_table = from_column.get_table(); + + // Check if this is a WEB_SITE or WEB_PAGE table + if from_table == ColumnTable::WebPage || from_table == ColumnTable::WebSite { + return generate_web_join_key(from_column, random_number_stream, join_count, scaling); + } + + // Returns tables: date is computed as sale_date + lag + // Based on JoinKeyUtils.java:generateDateReturnsJoinKey (lines 192-211) + if from_table == ColumnTable::StoreReturns + || from_table == ColumnTable::CatalogReturns + || from_table == ColumnTable::WebReturns + { + return generate_date_returns_join_key(from_table, random_number_stream, join_count); + } + + // Sales tables: use SALES weights + // Default: use SALES weights (most common case) + let weights = if Date::is_leap_year(year) { + CalendarWeights::SalesLeapYear + } else { + CalendarWeights::Sales + }; + + let day_number = CalendarDistribution::pick_random_day_of_year(weights, random_number_stream)?; + let result = Date::to_julian_days(&Date::new(year, 1, 1)) as i64 + day_number as i64; + Ok(if result > Date::JULIAN_TODAYS_DATE as i64 { + -1 + } else { + result + }) +} + +/// Generates a date join key for returns tables. +/// +/// Returns have a lag between the sale date and return date. +/// The lag is calculated as: random(min*2, max*2) days after the sale. +/// +/// Based on JoinKeyUtils.java:generateDateReturnsJoinKey (lines 192-211) +fn generate_date_returns_join_key( + from_table: crate::column::Table, + random_number_stream: &mut dyn RandomNumberStream, + join_count: i64, // This is the sale date (julian days) +) -> Result { + use crate::column::Table as ColumnTable; + + let (min, max) = match from_table { + ColumnTable::StoreReturns | ColumnTable::CatalogReturns => { + (CS_MIN_SHIP_DELAY, CS_MAX_SHIP_DELAY) + } + ColumnTable::WebReturns => (1, 120), // Web returns have 1-120 day ship lag + _ => { + return Err(TpcdsError::new(&format!( + "Invalid table for date returns join: {:?}", + from_table + ))) + } + }; + + let lag = + RandomValueGenerator::generate_uniform_random_int(min * 2, max * 2, random_number_stream); + Ok(join_count + lag as i64) +} + +/// Generates a join key to the time_dim table. +/// +/// Different table types use different hour selection strategies: +/// - Store sales/returns use STORE weights (typical store hours) +/// - Catalog/web sales/returns use CATALOG_AND_WEB weights (24/7 operations) +/// - Other tables use UNIFORM weights +/// +/// Returns seconds since midnight (0 to 86399). +/// +/// Based on JoinKeyUtils.java:generateTimeJoinKey (lines 213-235) +fn generate_time_join_key( + from_column: &dyn GeneratorColumn, + random_number_stream: &mut dyn RandomNumberStream, +) -> Result { + use crate::column::Table as ColumnTable; + + let from_table = from_column.get_table(); + + let weights = match from_table { + ColumnTable::StoreSales | ColumnTable::StoreReturns => HoursWeights::Store, + ColumnTable::CatalogSales + | ColumnTable::CatalogReturns + | ColumnTable::WebSales + | ColumnTable::WebReturns => HoursWeights::CatalogAndWeb, + _ => HoursWeights::Uniform, + }; + + let hour = HoursDistribution::pick_random_hour(weights, random_number_stream)?; + let seconds = RandomValueGenerator::generate_uniform_random_int(0, 3599, random_number_stream); + + Ok((hour as i64 * 3600) + seconds as i64) +} + +/// Generates a join key to a slowly changing dimension (SCD) table. +/// +/// SCD tables keep history, so the join key must match the appropriate +/// version of the dimension based on the effective date. +fn generate_scd_join_key( + to_table: Table, + random_number_stream: &mut dyn RandomNumberStream, + julian_date: i64, + scaling: &Scaling, +) -> Result { + // Can't have a revision in the future + if julian_date > Date::JULIAN_DATA_END_DATE { + return Ok(-1); + } + + let id_count = scaling.get_id_count(to_table); + let unique_key = + RandomValueGenerator::generate_uniform_random_key(1, id_count, random_number_stream); + + // Match the surrogate key based on the julian date for SCD tables + let key = slowly_changing_dimension_utils::match_surrogate_key( + unique_key, + julian_date, + to_table, + scaling, + ); + + Ok(if key > scaling.get_row_count(to_table) { + -1 + } else { + key + }) +} + +/// Generates a join key for web-related tables (web_site, web_page). +/// +/// Web tables have complex date logic involving site creation, open, and close dates. +/// Based on JoinKeyUtils.java:generateWebJoinKey (lines 144-175) +fn generate_web_join_key( + from_column: &dyn GeneratorColumn, + random_number_stream: &mut dyn RandomNumberStream, + join_key: i64, + scaling: &Scaling, +) -> Result { + let global_column_number = from_column.get_global_column_number(); + + // WP_CREATION_DATE_SK (global column 371) + if global_column_number == 371 { + // Page creation has to happen outside of the page window, to assure a constant number of pages, + // so it occurs in the gap between site creation and the site's actual activity. For sites that are replaced + // in the time span of the data set, this will depend on whether they are the first version or the second + let site = (join_key / WEB_PAGES_PER_SITE as i64 + 1) as i32; + let web_site_duration = get_web_site_duration(scaling); + let min_result = Date::JULIAN_DATE_MINIMUM as i64 + - ((site as i64 * WEB_DATE_STAGGER) % web_site_duration / 2); + return Ok(RandomValueGenerator::generate_uniform_random_int( + min_result as i32, + Date::JULIAN_DATE_MINIMUM, + random_number_stream, + ) as i64); + } + + // WEB_OPEN_DATE for WebPage (global column 340) or WebSite (global column 452) + if global_column_number == 340 || global_column_number == 452 { + let web_site_duration = get_web_site_duration(scaling); + return Ok(Date::JULIAN_DATE_MINIMUM as i64 + - ((join_key * WEB_DATE_STAGGER) % web_site_duration / 2)); + } + + // WEB_CLOSE_DATE for WebPage (global column 341) or WebSite (global column 453) + if global_column_number == 341 || global_column_number == 453 { + let web_site_duration = get_web_site_duration(scaling); + let mut result = Date::JULIAN_DATE_MINIMUM as i64 + - ((join_key * WEB_DATE_STAGGER) % web_site_duration / 2); + result += -web_site_duration; // the -1 here and below are due to undefined values in the C code + + // the site is completely replaced, and this is the first site + if is_replaced(join_key) && !is_replacement(join_key) { + // the close date of the first site needs to align on a revision boundary + result -= -web_site_duration / 2; + } + return Ok(result); + } + + Err(TpcdsError::new(&format!( + "Invalid column for web join: global column {}", + global_column_number + ))) +} + +/// Calculates the duration of a web site based on concurrent sites. +/// Based on JoinKeyUtils.java:getWebSiteDuration (lines 177-180) +fn get_web_site_duration(scaling: &Scaling) -> i64 { + let concurrent_web_sites = PseudoTableScalingInfos::get_concurrent_web_sites(); + let row_count = concurrent_web_sites + .get_row_count_for_scale(scaling.get_scale()) + .expect("Failed to get row count for concurrent web sites"); + + (Date::JULIAN_DATE_MAXIMUM as i64 - Date::JULIAN_DATE_MINIMUM as i64) * row_count +} + +/// Checks if a web site is replaced (has an even join key). +/// Based on JoinKeyUtils.java:isReplaced (lines 182-185) +fn is_replaced(join_key: i64) -> bool { + (join_key % 2) == 0 +} + +/// Checks if a web site is a replacement (odd division by 2). +/// Based on JoinKeyUtils.java:isReplacement (lines 187-190) +fn is_replacement(join_key: i64) -> bool { + (join_key / 2 % 2) != 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_is_replaced() { + assert!(is_replaced(0)); + assert!(is_replaced(2)); + assert!(is_replaced(4)); + assert!(!is_replaced(1)); + assert!(!is_replaced(3)); + } + + #[test] + fn test_is_replacement() { + assert!(!is_replacement(0)); // 0/2=0, 0%2=0 + assert!(!is_replacement(1)); // 1/2=0, 0%2=0 + assert!(is_replacement(2)); // 2/2=1, 1%2=1 + assert!(is_replacement(3)); // 3/2=1, 1%2=1 + assert!(!is_replacement(4)); // 4/2=2, 2%2=0 + } + + #[test] + fn test_generate_time_join_key() { + use crate::generator::StoreSalesGeneratorColumn; + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let column = StoreSalesGeneratorColumn::SsSoldTimeSk; + let result = generate_time_join_key(&column, &mut stream).unwrap(); + + // Time keys should be in range [0, 86400) seconds in a day + assert!( + result >= 0 && result < 86400, + "Time key should be valid seconds in day" + ); + } + + #[test] + fn test_generate_time_join_key_deterministic() { + use crate::generator::StoreSalesGeneratorColumn; + let mut stream1 = RandomNumberStreamImpl::new(1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(1).unwrap(); + let column = StoreSalesGeneratorColumn::SsSoldTimeSk; + + let result1 = generate_time_join_key(&column, &mut stream1).unwrap(); + let result2 = generate_time_join_key(&column, &mut stream2).unwrap(); + + assert_eq!(result1, result2, "Same seed should produce same time key"); + } + + #[test] + fn test_catalog_page_join_key() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let scaling = Scaling::new(1.0); + + // Catalog page join key is now implemented (CatalogPageTypesDistribution ported) + let result = generate_catalog_page_join_key(&mut stream, 2451545, &scaling); + assert!(result.is_ok(), "Catalog page join should work now"); + + let key = result.unwrap(); + assert!(key > 0, "Key should be positive"); + } + + // NOTE: Test disabled until column::Table vs config::Table is resolved + // #[test] + // fn test_generate_date_returns_join_key() { + // let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + // let sale_date = Date::to_julian_days(&Date::new(2003, 1, 1)) as i64; + // + // let return_date = _generate_date_returns_join_key( + // Table::StoreReturns, + // &mut stream, + // sale_date, + // ) + // .unwrap(); + // + // // Return should be after sale + // assert!(return_date > sale_date, "Return date should be after sale date"); + // + // // Lag should be within expected range + // let lag = return_date - sale_date; + // assert!(lag >= (CS_MIN_SHIP_DELAY * 2) as i64 && lag <= (CS_MAX_SHIP_DELAY * 2) as i64); + // } +} diff --git a/tpcdsgen/src/lib.rs b/tpcdsgen/src/lib.rs new file mode 100644 index 00000000..0bbefad8 --- /dev/null +++ b/tpcdsgen/src/lib.rs @@ -0,0 +1,20 @@ +pub mod business_key_generator; +pub mod column; +pub mod config; +pub mod distribution; +pub mod error; +pub mod generator; +pub mod join_key_utils; +pub mod nulls; +pub mod output; +pub mod permutations; +pub mod pseudo_table_scaling_infos; +pub mod random; +pub mod row; +pub mod scaling_info; +pub mod slowly_changing_dimension_utils; +pub mod table; +pub mod table_flags; +pub mod types; + +pub use error::TpcdsError; diff --git a/tpcdsgen/src/main.rs b/tpcdsgen/src/main.rs new file mode 100644 index 00000000..d84b5b22 --- /dev/null +++ b/tpcdsgen/src/main.rs @@ -0,0 +1,364 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! TPC-DS Data Generator - Rust Implementation +//! +//! Generates TPC-DS benchmark data with byte-for-byte compatibility with the Java reference. + +use clap::Parser; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::Path; +use std::time::Instant; + +use tpcdsgen::config::{Options, Session, Table}; +use tpcdsgen::output::Iso8859Writer; +use tpcdsgen::row::*; +use tpcdsgen::types::Date; + +type Result = std::result::Result>; + +fn main() -> Result<()> { + let options = Options::parse(); + let session = options.to_session()?; + + println!("TPC-DS Data Generator (Rust)"); + println!("Scale factor: {}", session.get_scaling().get_scale()); + println!("Output directory: {}", session.get_target_directory()); + + let start = Instant::now(); + + if session.generate_only_one_table() { + let table = session.get_only_table_to_generate(); + generate_table(table, &session)?; + } else { + // Generate all main tables + for table in Table::main_tables() { + generate_table(table, &session)?; + } + } + + let elapsed = start.elapsed(); + println!("\nCompleted in {:.2}s", elapsed.as_secs_f64()); + + Ok(()) +} + +fn generate_table(table: Table, session: &Session) -> Result<()> { + match table { + // Simple dimension tables + Table::CallCenter => generate_simple::(table, session), + Table::CatalogPage => generate_simple::(table, session), + Table::Customer => generate_simple::(table, session), + Table::CustomerAddress => generate_simple::(table, session), + Table::CustomerDemographics => { + generate_simple::(table, session) + } + Table::DateDim => generate_simple::(table, session), + Table::DbgenVersion => generate_simple::(table, session), + Table::HouseholdDemographics => { + generate_simple::(table, session) + } + Table::IncomeBand => generate_simple::(table, session), + Table::Item => generate_simple::(table, session), + Table::Promotion => generate_simple::(table, session), + Table::Reason => generate_simple::(table, session), + Table::ShipMode => generate_simple::(table, session), + Table::Store => generate_simple::(table, session), + Table::TimeDim => generate_simple::(table, session), + Table::Warehouse => generate_simple::(table, session), + Table::WebPage => generate_simple::(table, session), + Table::WebSite => generate_simple::(table, session), + + // Sales + Returns pairs + Table::StoreSales => generate_store_sales(session), + Table::StoreReturns => Ok(()), // Generated with StoreSales + Table::CatalogSales => generate_catalog_sales(session), + Table::CatalogReturns => Ok(()), // Generated with CatalogSales + Table::WebSales => generate_web_sales(session), + Table::WebReturns => Ok(()), // Generated with WebSales + + // Special tables + Table::Inventory => generate_inventory(session), + + // Source tables - skip + _ => Ok(()), + } +} + +/// Trait for creating row generators +trait RowGeneratorFactory: RowGenerator + Sized { + fn create() -> Self; +} + +// Implement factory for all simple generators +macro_rules! impl_factory { + ($($gen:ty),*) => { + $( + impl RowGeneratorFactory for $gen { + fn create() -> Self { Self::new() } + } + )* + }; +} + +impl_factory!( + CallCenterRowGenerator, + CatalogPageRowGenerator, + CustomerRowGenerator, + CustomerAddressRowGenerator, + CustomerDemographicsRowGenerator, + DateDimRowGenerator, + DbgenVersionRowGenerator, + HouseholdDemographicsRowGenerator, + IncomeBandRowGenerator, + ItemRowGenerator, + PromotionRowGenerator, + ReasonRowGenerator, + ShipModeRowGenerator, + StoreRowGenerator, + TimeDimRowGenerator, + WarehouseRowGenerator, + WebPageRowGenerator, + WebSiteRowGenerator +); + +/// Generate a simple table (one row per row_number, no child tables) +fn generate_simple(table: Table, session: &Session) -> Result<()> { + let mut generator = G::create(); + let row_count = session.get_scaling().get_row_count(table); + + let path = get_output_path(table, session); + let file = File::create(&path)?; + let mut writer = Iso8859Writer::new(BufWriter::new(file)); + + print!("Generating {}... ", table.get_name()); + std::io::stdout().flush()?; + + for row_number in 1..=row_count { + let result = generator.generate_row_and_child_rows(row_number, session, None, None)?; + + for row in result.get_rows() { + row.write_to(&mut writer, session.get_separator())?; + } + + generator.consume_remaining_seeds_for_row(); + } + + writer.flush()?; + println!("{} rows -> {}", row_count, path.display()); + + Ok(()) +} + +/// Generate store_sales and store_returns together +fn generate_store_sales(session: &Session) -> Result<()> { + let mut generator = StoreSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::StoreSales); + + let sales_path = get_output_path(Table::StoreSales, session); + let returns_path = get_output_path(Table::StoreReturns, session); + + let mut sales_writer = Iso8859Writer::new(BufWriter::new(File::create(&sales_path)?)); + let mut returns_writer = Iso8859Writer::new(BufWriter::new(File::create(&returns_path)?)); + + print!("Generating store_sales + store_returns... "); + std::io::stdout().flush()?; + + let mut sales_count = 0i64; + let mut returns_count = 0i64; + let mut row_number = 1i64; + + while row_number <= num_orders { + let result = generator.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + + if !rows.is_empty() { + rows[0].write_to(&mut sales_writer, session.get_separator())?; + sales_count += 1; + } + + if rows.len() > 1 { + rows[1].write_to(&mut returns_writer, session.get_separator())?; + returns_count += 1; + } + + if result.should_end_row() { + generator.consume_remaining_seeds_for_row(); + generator.consume_child_seeds(); + row_number += 1; + } + } + + sales_writer.flush()?; + returns_writer.flush()?; + + println!( + "{} sales, {} returns -> {}, {}", + sales_count, + returns_count, + sales_path.display(), + returns_path.display() + ); + + Ok(()) +} + +/// Generate catalog_sales and catalog_returns together +fn generate_catalog_sales(session: &Session) -> Result<()> { + let mut generator = CatalogSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::CatalogSales); + + let sales_path = get_output_path(Table::CatalogSales, session); + let returns_path = get_output_path(Table::CatalogReturns, session); + + let mut sales_writer = Iso8859Writer::new(BufWriter::new(File::create(&sales_path)?)); + let mut returns_writer = Iso8859Writer::new(BufWriter::new(File::create(&returns_path)?)); + + print!("Generating catalog_sales + catalog_returns... "); + std::io::stdout().flush()?; + + let mut sales_count = 0i64; + let mut returns_count = 0i64; + let mut row_number = 1i64; + + while row_number <= num_orders { + let result = generator.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + + if !rows.is_empty() { + rows[0].write_to(&mut sales_writer, session.get_separator())?; + sales_count += 1; + } + + if rows.len() > 1 { + rows[1].write_to(&mut returns_writer, session.get_separator())?; + returns_count += 1; + } + + if result.should_end_row() { + generator.consume_remaining_seeds_for_row(); + generator.consume_child_seeds(); + row_number += 1; + } + } + + sales_writer.flush()?; + returns_writer.flush()?; + + println!( + "{} sales, {} returns -> {}, {}", + sales_count, + returns_count, + sales_path.display(), + returns_path.display() + ); + + Ok(()) +} + +/// Generate web_sales and web_returns together +fn generate_web_sales(session: &Session) -> Result<()> { + let mut generator = WebSalesRowGenerator::new(); + let num_orders = session.get_scaling().get_row_count(Table::WebSales); + + let sales_path = get_output_path(Table::WebSales, session); + let returns_path = get_output_path(Table::WebReturns, session); + + let mut sales_writer = Iso8859Writer::new(BufWriter::new(File::create(&sales_path)?)); + let mut returns_writer = Iso8859Writer::new(BufWriter::new(File::create(&returns_path)?)); + + print!("Generating web_sales + web_returns... "); + std::io::stdout().flush()?; + + let mut sales_count = 0i64; + let mut returns_count = 0i64; + let mut row_number = 1i64; + + while row_number <= num_orders { + let result = generator.generate_row_and_child_rows(row_number, session, None, None)?; + let rows = result.get_rows(); + + if !rows.is_empty() { + rows[0].write_to(&mut sales_writer, session.get_separator())?; + sales_count += 1; + } + + if rows.len() > 1 { + rows[1].write_to(&mut returns_writer, session.get_separator())?; + returns_count += 1; + } + + if result.should_end_row() { + generator.consume_remaining_seeds_for_row(); + generator.consume_child_seeds(); + row_number += 1; + } + } + + sales_writer.flush()?; + returns_writer.flush()?; + + println!( + "{} sales, {} returns -> {}, {}", + sales_count, + returns_count, + sales_path.display(), + returns_path.display() + ); + + Ok(()) +} + +/// Generate inventory table (special row count calculation) +fn generate_inventory(session: &Session) -> Result<()> { + let mut generator = InventoryRowGenerator::new(); + let scaling = session.get_scaling(); + + let item_count = scaling.get_id_count(tpcdsgen::config::Table::Item); + let warehouse_count = scaling.get_row_count(tpcdsgen::config::Table::Warehouse); + let n_days = Date::JULIAN_DATE_MAXIMUM - Date::JULIAN_DATE_MINIMUM; + let n_weeks = (n_days + 7) / 7; + let num_rows = item_count * warehouse_count * n_weeks as i64; + + let path = get_output_path(Table::Inventory, session); + let mut writer = Iso8859Writer::new(BufWriter::new(File::create(&path)?)); + + print!("Generating inventory... "); + std::io::stdout().flush()?; + + for row_number in 1..=num_rows { + let result = generator.generate_row_and_child_rows(row_number, session, None, None)?; + + for row in result.get_rows() { + row.write_to(&mut writer, session.get_separator())?; + } + + generator.consume_remaining_seeds_for_row(); + } + + writer.flush()?; + println!("{} rows -> {}", num_rows, path.display()); + + Ok(()) +} + +/// Get output file path for a table +fn get_output_path(table: Table, session: &Session) -> std::path::PathBuf { + Path::new(session.get_target_directory()).join(format!( + "{}{}", + table.get_name(), + session.get_suffix() + )) +} diff --git a/tpcdsgen/src/nulls.rs b/tpcdsgen/src/nulls.rs new file mode 100644 index 00000000..3816cc7d --- /dev/null +++ b/tpcdsgen/src/nulls.rs @@ -0,0 +1,143 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Null value generation utilities for TPC-DS tables. +//! +//! This module provides functionality to create null bitmaps for table rows +//! based on each table's null probability settings. + +use crate::random::{RandomNumberStream, RandomValueGenerator}; +use crate::table::Table; + +/// Creates a null bitmap for a table row based on the table's null probability. +/// +/// The function generates a random threshold and bitmap. If the threshold is less +/// than the table's null basis points (probability of nulls), it returns a bitmap +/// indicating which columns can be null (respecting the table's not-null constraints). +/// +/// # Arguments +/// +/// * `table` - The table for which to generate the null bitmap +/// * `random_number_stream` - The random number stream for generating values +/// +/// # Returns +/// +/// A 64-bit bitmap where each bit represents whether a column should be null. +/// Returns 0 if no columns should be null. +/// +/// # Examples +/// +/// ``` +/// use tpcdsgen::table::Table; +/// use tpcdsgen::random::RandomNumberStreamImpl; +/// use tpcdsgen::nulls::create_null_bit_map; +/// +/// let mut stream = RandomNumberStreamImpl::new(1).unwrap(); +/// let null_bitmap = create_null_bit_map(Table::CallCenter, &mut stream); +/// // null_bitmap will be 0 or a value respecting CallCenter's not-null constraints +/// ``` +pub fn create_null_bit_map(table: Table, random_number_stream: &mut dyn RandomNumberStream) -> i64 { + let threshold = + RandomValueGenerator::generate_uniform_random_int(0, 9999, random_number_stream); + let bit_map = + RandomValueGenerator::generate_uniform_random_key(1, i32::MAX as i64, random_number_stream); + + // Set the bitmap based on threshold and NOT NULL definitions + if threshold < table.get_null_basis_points() { + return bit_map & !table.get_not_null_bit_map(); + } + + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + + #[test] + fn test_create_null_bit_map_for_table_with_zero_null_basis_points() { + // Tables with 0 null basis points should always return 0 + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // IncomeBand has 0 null basis points + let null_bitmap = create_null_bit_map(Table::IncomeBand, &mut stream); + assert_eq!( + null_bitmap, 0, + "Table with 0 null basis points should always return 0 bitmap" + ); + } + + #[test] + fn test_create_null_bit_map_respects_not_null_constraints() { + // Generate multiple bitmaps and verify they respect not-null constraints + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // CallCenter has null basis points of 100 and not-null bitmap of 0xB + for _ in 0..10 { + let null_bitmap = create_null_bit_map(Table::CallCenter, &mut stream); + + if null_bitmap != 0 { + // If bitmap is non-zero, verify it respects not-null constraints + let not_null_bitmap = Table::CallCenter.get_not_null_bit_map(); + assert_eq!( + null_bitmap & not_null_bitmap, + 0, + "Null bitmap should not set bits that are in not-null bitmap" + ); + } + } + } + + #[test] + fn test_create_null_bit_map_produces_varied_results() { + // Verify that the function can produce both zero and non-zero results + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let mut has_zero = false; + let mut has_non_zero = false; + + // Warehouse has null basis points of 100, so should sometimes generate nulls + for _ in 0..100 { + let null_bitmap = create_null_bit_map(Table::Warehouse, &mut stream); + if null_bitmap == 0 { + has_zero = true; + } else { + has_non_zero = true; + } + if has_zero && has_non_zero { + break; + } + } + + assert!( + has_zero || has_non_zero, + "Should produce at least some results (either zero or non-zero)" + ); + } + + #[test] + fn test_create_null_bit_map_deterministic() { + // Same seed should produce same results + let mut stream1 = RandomNumberStreamImpl::new(1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(1).unwrap(); + + let bitmap1 = create_null_bit_map(Table::CallCenter, &mut stream1); + let bitmap2 = create_null_bit_map(Table::CallCenter, &mut stream2); + + assert_eq!( + bitmap1, bitmap2, + "Same random stream should produce same null bitmap" + ); + } +} diff --git a/tpcdsgen/src/output.rs b/tpcdsgen/src/output.rs new file mode 100644 index 00000000..0d2c896a --- /dev/null +++ b/tpcdsgen/src/output.rs @@ -0,0 +1,185 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Output utilities for TPC-DS data generation +//! +//! The Java implementation reads distribution files as ISO-8859-1 (Latin-1) and +//! writes output files as ISO-8859-1 (see TableGenerator.java line 80). +//! +//! Rust reads ISO-8859-1 bytes and converts them to UTF-8 strings (since Rust +//! strings are UTF-8). For byte-for-byte compatibility with Java output, we must +//! convert back to ISO-8859-1 when writing. +//! +//! Since ISO-8859-1 bytes 0x00-0xFF map directly to Unicode code points U+0000-U+00FF, +//! any character from the distribution files can be safely converted back to a single byte. + +use std::io::{self, Write}; + +/// A wrapper that implements std::io::Write by appending to a String. +/// +/// This allows using `write!` macro with a String buffer, which can then +/// be converted to ISO-8859-1 and written to the output. +pub struct StringWriter<'a>(pub &'a mut String); + +impl<'a> Write for StringWriter<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result { + // buf contains UTF-8 bytes from write! macro + let s = + std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + self.0.push_str(s); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Converts a UTF-8 string to ISO-8859-1 bytes. +/// +/// This is the inverse of the conversion done in file_loader.rs when reading +/// distribution files. Characters must be in the range U+0000-U+00FF. +/// +/// # Errors +/// Returns an error if any character is outside the ISO-8859-1 range (U+0000-U+00FF). +pub fn to_iso_8859_1(s: &str) -> io::Result> { + s.chars() + .map(|c| { + let code = c as u32; + if code > 255 { + Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Character '{}' (U+{:04X}) is outside ISO-8859-1 range", + c, code + ), + )) + } else { + Ok(code as u8) + } + }) + .collect() +} + +/// A writer wrapper that converts UTF-8 strings to ISO-8859-1 before writing. +/// +/// This matches Java's behavior in TableGenerator.java which writes output +/// using StandardCharsets.ISO_8859_1. +pub struct Iso8859Writer { + inner: W, +} + +impl Iso8859Writer { + pub fn new(writer: W) -> Self { + Iso8859Writer { inner: writer } + } + + /// Write a string as ISO-8859-1 bytes + pub fn write_str(&mut self, s: &str) -> io::Result<()> { + let bytes = to_iso_8859_1(s)?; + self.inner.write_all(&bytes) + } + + /// Write a string followed by a newline as ISO-8859-1 bytes + pub fn write_line(&mut self, s: &str) -> io::Result<()> { + self.write_str(s)?; + self.inner.write_all(b"\n") + } + + /// Flush the underlying writer + pub fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +/// Implement std::io::Write for Iso8859Writer so it can be used with write! macro +/// and TableRow::write_to(). +/// +/// The input bytes are expected to be valid UTF-8 (as produced by write! macro). +/// Each UTF-8 character is converted to its ISO-8859-1 equivalent. +impl Write for Iso8859Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + // Interpret input as UTF-8, convert to ISO-8859-1 + let s = + std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let iso_bytes = to_iso_8859_1(s)?; + self.inner.write_all(&iso_bytes)?; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_iso_8859_1_ascii() { + let result = to_iso_8859_1("Hello").unwrap(); + assert_eq!(result, b"Hello"); + } + + #[test] + fn test_to_iso_8859_1_latin1() { + // Ô is U+00D4, which should become byte 0xD4 + let result = to_iso_8859_1("CÔTE D'IVOIRE").unwrap(); + assert_eq!(result[1], 0xD4); // The Ô character + assert_eq!(result.len(), 13); // One byte per character + } + + #[test] + fn test_iso8859_writer() { + let mut buffer = Vec::new(); + { + let mut writer = Iso8859Writer::new(&mut buffer); + writer.write_line("CÔTE D'IVOIRE").unwrap(); + } + // Verify Ô (U+00D4) is written as single byte 0xD4, not UTF-8 (0xC3 0x94) + assert_eq!(buffer[1], 0xD4); + assert_eq!(buffer.len(), 14); // 13 chars + newline + } + + #[test] + fn test_to_iso_8859_1_out_of_range() { + // Euro sign € is U+20AC, outside ISO-8859-1 range + let result = to_iso_8859_1("€100"); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + assert!(err.to_string().contains("outside ISO-8859-1 range")); + } + + #[test] + fn test_string_writer() { + let mut buffer = String::new(); + { + let mut writer = StringWriter(&mut buffer); + write!(writer, "Hello, {}!", "World").unwrap(); + } + assert_eq!(buffer, "Hello, World!"); + } + + #[test] + fn test_string_writer_with_numbers() { + let mut buffer = String::new(); + { + let mut writer = StringWriter(&mut buffer); + write!(writer, "{}|{}|{}", 42, "test", 3.14).unwrap(); + } + assert_eq!(buffer, "42|test|3.14"); + } +} diff --git a/tpcdsgen/src/permutations.rs b/tpcdsgen/src/permutations.rs new file mode 100644 index 00000000..2a329e30 --- /dev/null +++ b/tpcdsgen/src/permutations.rs @@ -0,0 +1,220 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Permutation utilities for TPC-DS data generation. +//! +//! This module provides functionality to create random permutations, which are used +//! in sales table generation to ensure unique item selection within orders. + +use crate::random::{RandomNumberStream, RandomValueGenerator}; + +/// Creates a random permutation of integers from 0 to size-1. +/// +/// Uses the Fisher-Yates shuffle algorithm to generate a random permutation. +/// The permutation is an array where each position contains a unique value +/// from 0 to size-1. +/// +/// # Arguments +/// +/// * `size` - The number of elements in the permutation +/// * `stream` - The random number stream for generating random indices +/// +/// # Returns +/// +/// A vector containing a random permutation of integers [0, 1, 2, ..., size-1] +/// +/// # Examples +/// +/// ``` +/// use tpcdsgen::random::RandomNumberStreamImpl; +/// use tpcdsgen::permutations::make_permutation; +/// +/// let mut stream = RandomNumberStreamImpl::new(1).unwrap(); +/// let perm = make_permutation(5, &mut stream); +/// // perm contains [0,1,2,3,4] in some random order +/// assert_eq!(perm.len(), 5); +/// ``` +pub fn make_permutation(size: usize, stream: &mut dyn RandomNumberStream) -> Vec { + // Initialize array with sequential values [0, 1, 2, ..., size-1] + let mut number_set: Vec = (0..size as i32).collect(); + + // Fisher-Yates shuffle + for i in 0..number_set.len() { + let index = RandomValueGenerator::generate_uniform_random_int(0, (size - 1) as i32, stream) + as usize; + number_set.swap(i, index); + } + + number_set +} + +/// Gets an entry from a permutation using 1-based indexing. +/// +/// **Important**: This function uses 1-based indexing (as per TPC-DS spec). +/// The returned value is also 1-based (permutation value + 1). +/// +/// # Arguments +/// +/// * `permutation` - The permutation array +/// * `index` - The 1-based index (must be >= 1) +/// +/// # Returns +/// +/// The value at the given index, incremented by 1 (to convert to 1-based) +/// +/// # Panics +/// +/// Panics if index < 1 +/// +/// # Examples +/// +/// ``` +/// use tpcdsgen::permutations::get_permutation_entry; +/// +/// let perm = vec![2, 0, 1, 3]; +/// let value = get_permutation_entry(&perm, 1); +/// // value is perm[0] + 1 = 2 + 1 = 3 +/// assert_eq!(value, 3); +/// ``` +pub fn get_permutation_entry(permutation: &[i32], index: i32) -> i32 { + assert!(index >= 1, "index must be >= 1, got: {}", index); + permutation[(index - 1) as usize] + 1 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::RandomNumberStreamImpl; + use std::collections::HashSet; + + #[test] + fn test_make_permutation_correct_size() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let perm = make_permutation(10, &mut stream); + assert_eq!(perm.len(), 10, "Permutation should have correct size"); + } + + #[test] + fn test_make_permutation_contains_all_values() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let size = 20; + let perm = make_permutation(size, &mut stream); + + // Convert to set to check for uniqueness and completeness + let perm_set: HashSet = perm.iter().cloned().collect(); + + assert_eq!( + perm_set.len(), + size, + "Permutation should contain all unique values" + ); + + // Check that all values from 0 to size-1 are present + for i in 0..size as i32 { + assert!( + perm_set.contains(&i), + "Permutation should contain value {}", + i + ); + } + } + + #[test] + fn test_make_permutation_is_randomized() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let perm = make_permutation(10, &mut stream); + + // Check that it's not just the identity permutation [0,1,2,3,...] + let is_identity = perm.iter().enumerate().all(|(i, &val)| val == i as i32); + + // Note: There's a small chance this could fail if random shuffle produces identity + // But with 10 elements, probability is 1/10! which is negligible + assert!( + !is_identity, + "Permutation should be shuffled (not identity)" + ); + } + + #[test] + fn test_make_permutation_deterministic() { + // Same seed should produce same permutation + let mut stream1 = RandomNumberStreamImpl::new(1).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(1).unwrap(); + + let perm1 = make_permutation(10, &mut stream1); + let perm2 = make_permutation(10, &mut stream2); + + assert_eq!( + perm1, perm2, + "Same random seed should produce identical permutation" + ); + } + + #[test] + fn test_get_permutation_entry_correct_value() { + let perm = vec![2, 0, 1, 3]; + + // Test 1-based indexing: index 1 gets perm[0] + 1 + assert_eq!(get_permutation_entry(&perm, 1), 3); // perm[0] = 2, +1 = 3 + assert_eq!(get_permutation_entry(&perm, 2), 1); // perm[1] = 0, +1 = 1 + assert_eq!(get_permutation_entry(&perm, 3), 2); // perm[2] = 1, +1 = 2 + assert_eq!(get_permutation_entry(&perm, 4), 4); // perm[3] = 3, +1 = 4 + } + + #[test] + #[should_panic(expected = "index must be >= 1")] + fn test_get_permutation_entry_rejects_zero_index() { + let perm = vec![0, 1, 2]; + get_permutation_entry(&perm, 0); + } + + #[test] + #[should_panic(expected = "index must be >= 1")] + fn test_get_permutation_entry_rejects_negative_index() { + let perm = vec![0, 1, 2]; + get_permutation_entry(&perm, -1); + } + + #[test] + fn test_permutation_integration() { + // Test the full workflow: create permutation, access entries + let mut stream = RandomNumberStreamImpl::new(42).unwrap(); + let size = 5; + let perm = make_permutation(size, &mut stream); + + // Access all entries using 1-based indexing + let mut values = Vec::new(); + for i in 1..=size as i32 { + values.push(get_permutation_entry(&perm, i)); + } + + // All values should be in range [1, size] + for &val in &values { + assert!( + val >= 1 && val <= size as i32, + "Value {} should be in range [1, {}]", + val, + size + ); + } + + // All values should be unique + let value_set: HashSet = values.iter().cloned().collect(); + assert_eq!( + value_set.len(), + size, + "All accessed values should be unique" + ); + } +} diff --git a/tpcdsgen/src/pseudo_table_scaling_infos.rs b/tpcdsgen/src/pseudo_table_scaling_infos.rs new file mode 100644 index 00000000..93364fb0 --- /dev/null +++ b/tpcdsgen/src/pseudo_table_scaling_infos.rs @@ -0,0 +1,48 @@ +use crate::scaling_info::{ScalingInfo, ScalingModel}; + +/// Pseudo table scaling information (PseudoTableScalingInfos) +pub struct PseudoTableScalingInfos; + +impl PseudoTableScalingInfos { + pub fn get_concurrent_web_sites() -> ScalingInfo { + ScalingInfo::new( + 0, + ScalingModel::Logarithmic, + &[0, 2, 3, 4, 5, 5, 5, 5, 5, 5], + 0, + ) + .expect("Failed to create CONCURRENT_WEB_SITES scaling info") + } + + pub fn get_active_cities() -> ScalingInfo { + ScalingInfo::new( + 0, + ScalingModel::Logarithmic, + &[0, 2, 6, 18, 30, 54, 90, 165, 270, 495], + 0, + ) + .expect("Failed to create ACTIVE_CITIES scaling info") + } + + pub fn get_active_counties() -> ScalingInfo { + ScalingInfo::new( + 0, + ScalingModel::Logarithmic, + &[0, 1, 3, 9, 15, 27, 45, 81, 135, 245], + 0, + ) + .expect("Failed to create ACTIVE_COUNTIES scaling info") + } + + pub fn get_active_cities_row_count_for_scale(scale: f64) -> i64 { + Self::get_active_cities() + .get_row_count_for_scale(scale) + .expect("Failed to get active cities row count") + } + + pub fn get_active_counties_row_count_for_scale(scale: f64) -> i64 { + Self::get_active_counties() + .get_row_count_for_scale(scale) + .expect("Failed to get active counties row count") + } +} diff --git a/tpcdsgen/src/random/generator.rs b/tpcdsgen/src/random/generator.rs new file mode 100644 index 00000000..06a91711 --- /dev/null +++ b/tpcdsgen/src/random/generator.rs @@ -0,0 +1,443 @@ +use crate::random::stream::RandomNumberStream; +use crate::types::{Date, Decimal}; + +pub struct RandomValueGenerator; + +impl RandomValueGenerator { + pub const ALPHA_NUMERIC: &'static str = + "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"; + pub const DIGITS: &'static str = "0123456789"; + + // Static byte arrays to avoid repeated .chars().collect() allocations + const ALPHA_NUMERIC_BYTES: &'static [u8] = + b"abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789"; + const DIGITS_BYTES: &'static [u8] = b"0123456789"; + + pub fn generate_uniform_random_int( + min: i32, + max: i32, + random_number_stream: &mut dyn RandomNumberStream, + ) -> i32 { + // truncating long to int copies behavior of c code. + let mut result = random_number_stream.next_random() as i32; + result %= max - min + 1; + result += min; + result + } + + pub fn generate_uniform_random_key( + min: i64, + max: i64, + random_number_stream: &mut dyn RandomNumberStream, + ) -> i64 { + // truncating long to int copies behavior of c code + let mut result = random_number_stream.next_random() as i32; + result %= (max - min + 1) as i32; + result += min as i32; + result as i64 + } + + pub fn generate_uniform_random_decimal( + min: Decimal, + max: Decimal, + random_number_stream: &mut dyn RandomNumberStream, + ) -> Decimal { + let precision = if min.get_precision() < max.get_precision() { + min.get_precision() + } else { + max.get_precision() + }; + + // compute number + let mut number = random_number_stream.next_random(); + number %= max.get_number() - min.get_number() + 1; + number += min.get_number(); + + Decimal::new(number, precision).unwrap() + } + + pub fn generate_uniform_random_date( + min: Date, + max: Date, + random_number_stream: &mut dyn RandomNumberStream, + ) -> crate::error::Result { + let range = max.to_julian_days() - min.to_julian_days(); + let julian_days = min.to_julian_days() + + Self::generate_uniform_random_int(0, range, random_number_stream); + Ok(Date::from_julian_days(julian_days)) + } + + // Generate random string from a byte slice (optimized for ASCII character sets) + fn generate_random_from_bytes( + length: usize, + bytes: &[u8], + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + let mut result = Vec::with_capacity(length); + + for _ in 0..length { + let index = + Self::generate_uniform_random_int(0, bytes.len() as i32 - 1, random_number_stream); + result.push(bytes[index as usize]); + } + + // Safety: ALPHA_NUMERIC_BYTES and DIGITS_BYTES contain only ASCII bytes + unsafe { String::from_utf8_unchecked(result) } + } + + // Generate random string of specified length from given character set + // For non-ASCII character sets that require UTF-8 char handling + pub fn generate_random_string( + length: usize, + character_set: &str, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + let chars: Vec = character_set.chars().collect(); + let mut result = String::with_capacity(length); + + for _ in 0..length { + let index = + Self::generate_uniform_random_int(0, chars.len() as i32 - 1, random_number_stream); + result.push(chars[index as usize]); + } + + result + } + + // Generate random alphanumeric string (optimized with static byte array) + pub fn generate_random_alphanumeric( + length: usize, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + Self::generate_random_from_bytes(length, Self::ALPHA_NUMERIC_BYTES, random_number_stream) + } + + // Generate random numeric string (optimized with static byte array) + pub fn generate_random_digits( + length: usize, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + Self::generate_random_from_bytes(length, Self::DIGITS_BYTES, random_number_stream) + } + + // Generate random charset string with variable length (generateRandomCharset) + // This method loops to max to consume the same number of random values as the Java implementation + pub fn generate_random_charset( + character_set: &str, + min: i32, + max: i32, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + // Optimize for ALPHA_NUMERIC which is the common case + if character_set == Self::ALPHA_NUMERIC { + return Self::generate_random_charset_bytes( + Self::ALPHA_NUMERIC_BYTES, + min, + max, + random_number_stream, + ); + } + + let length = Self::generate_uniform_random_int(min, max, random_number_stream); + let chars: Vec = character_set.chars().collect(); + let mut result = String::with_capacity(length as usize); + + // Loop to max to consume the same number of random values (behavior) + for i in 0..max { + let index = + Self::generate_uniform_random_int(0, chars.len() as i32 - 1, random_number_stream); + if i < length { + result.push(chars[index as usize]); + } + } + + result + } + + // Optimized version using byte array for ASCII character sets + fn generate_random_charset_bytes( + bytes: &[u8], + min: i32, + max: i32, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + let length = Self::generate_uniform_random_int(min, max, random_number_stream); + let mut result = Vec::with_capacity(length as usize); + + // Loop to max to consume the same number of random values (behavior) + for i in 0..max { + let index = + Self::generate_uniform_random_int(0, bytes.len() as i32 - 1, random_number_stream); + if i < length { + result.push(bytes[index as usize]); + } + } + + // Safety: bytes contain only ASCII + unsafe { String::from_utf8_unchecked(result) } + } + + // Generate random boolean with given probability (0.0 to 1.0) + pub fn generate_random_boolean( + probability: f64, + random_number_stream: &mut dyn RandomNumberStream, + ) -> bool { + random_number_stream.next_random_double() < probability + } + + // Generate random weighted selection from array (indices) + pub fn generate_weighted_random_index( + weights: &[i32], + random_number_stream: &mut dyn RandomNumberStream, + ) -> usize { + let total_weight: i32 = weights.iter().sum(); + let random_value = Self::generate_uniform_random_int(1, total_weight, random_number_stream); + + let mut cumulative_weight = 0; + for (index, &weight) in weights.iter().enumerate() { + cumulative_weight += weight; + if random_value <= cumulative_weight { + return index; + } + } + + // Fallback to last index (should not happen with proper weights) + weights.len() - 1 + } + + // Generate random text following Java implementation exactly + pub fn generate_random_text( + min_length: i32, + max_length: i32, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + let mut is_sentence_beginning = true; + let mut text = String::new(); + let mut target_length = + Self::generate_uniform_random_int(min_length, max_length, random_number_stream); + + while target_length > 0 { + let mut generated = Self::generate_random_sentence(random_number_stream); + if is_sentence_beginning && !generated.is_empty() { + let first_char = generated + .chars() + .next() + .unwrap() + .to_uppercase() + .collect::(); + generated = first_char + &generated[1..]; + } + + let generated_length = generated.len() as i32; + is_sentence_beginning = generated.ends_with('.'); + + // truncate so as not to exceed target length + if target_length < generated_length { + generated = generated[..target_length as usize].to_string(); + } + + target_length -= generated_length; + + text.push_str(&generated); + if target_length > 0 { + text.push(' '); + target_length -= 1; + } + } + + text + } + + /// Generate a random URL (generateRandomUrl) + /// This is what the C code does. No joke. It always returns the same value. + pub fn generate_random_url(_random_number_stream: &mut dyn RandomNumberStream) -> String { + "http://www.foo.com".to_string() + } + + /// Generate a random email address (generateRandomEmail) + pub fn generate_random_email( + first: &str, + last: &str, + random_number_stream: &mut dyn RandomNumberStream, + ) -> String { + use crate::distribution::TopDomainsDistribution; + + let domain = TopDomainsDistribution::pick_random_top_domain(random_number_stream) + .unwrap_or_else(|_| "com".to_string()); + let company_length = + Self::generate_uniform_random_int(10, 20, random_number_stream) as usize; + let company = + Self::generate_random_charset(Self::ALPHA_NUMERIC, 1, 20, random_number_stream); + let company = if company.len() < company_length { + company + } else { + company[..company_length].to_string() + }; + + format!("{}.{}@{}.{}", first, last, company, domain) + } + + // Generate random sentence following Java implementation exactly + fn generate_random_sentence(random_number_stream: &mut dyn RandomNumberStream) -> String { + use crate::distribution::*; + + let mut verbiage = String::new(); + let syntax = pick_random_sentence(random_number_stream).unwrap_or("N V."); + + for ch in syntax.chars() { + match ch { + 'N' => verbiage.push_str(pick_random_noun(random_number_stream).unwrap_or("thing")), + 'V' => verbiage.push_str(pick_random_verb(random_number_stream).unwrap_or("is")), + 'J' => { + verbiage.push_str(pick_random_adjective(random_number_stream).unwrap_or("good")) + } + 'D' => { + verbiage.push_str(pick_random_adverb(random_number_stream).unwrap_or("well")) + } + 'X' => { + verbiage.push_str(pick_random_auxiliary(random_number_stream).unwrap_or("can")) + } + 'P' => { + verbiage.push_str(pick_random_preposition(random_number_stream).unwrap_or("to")) + } + 'A' => { + verbiage.push_str(pick_random_article(random_number_stream).unwrap_or("the")) + } + 'T' => { + verbiage.push_str(pick_random_terminator(random_number_stream).unwrap_or(".")) + } + _ => verbiage.push(ch), // this is for adding punctuation and white space. + } + } + + verbiage + } + + /// Generate word based on seed and syllables distribution (exact Java implementation) + /// Takes the distribution as parameter to support both SYLLABLES_DISTRIBUTION and + /// BRAND_SYLLABLES_DISTRIBUTION + pub fn generate_word( + seed: i64, + max_chars: i32, + distribution: &crate::distribution::FileBasedStringValuesDistribution, + ) -> String { + let size = distribution.get_size() as i64; + let mut word = String::new(); + let mut seed = seed; + + while seed > 0 { + let syllable = distribution + .get_value_at_index(0, (seed % size) as usize) + .unwrap_or("syl"); + seed /= size; + + if (word.len() + syllable.len()) <= max_chars as usize { + word.push_str(syllable); + } else { + break; + } + } + + word + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::random::stream::RandomNumberStreamImpl; + + #[test] + fn test_uniform_random_int() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let result = RandomValueGenerator::generate_uniform_random_int(1, 10, &mut stream); + assert!(result >= 1 && result <= 10); + } + + #[test] + fn test_uniform_random_key() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let result = RandomValueGenerator::generate_uniform_random_key(100, 200, &mut stream); + assert!(result >= 100 && result <= 200); + } + + #[test] + fn test_uniform_random_decimal() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let min = Decimal::new(100, 2).unwrap(); // 1.00 + let max = Decimal::new(500, 2).unwrap(); // 5.00 + let result = RandomValueGenerator::generate_uniform_random_decimal(min, max, &mut stream); + + assert!(result.get_number() >= min.get_number() && result.get_number() <= max.get_number()); + assert_eq!(result.get_precision(), 2); + } + + #[test] + fn test_uniform_random_date() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let min = Date::new(2020, 1, 1); + let max = Date::new(2020, 12, 31); + let result = + RandomValueGenerator::generate_uniform_random_date(min, max, &mut stream).unwrap(); + + assert!(result.to_julian_days() >= min.to_julian_days()); + assert!(result.to_julian_days() <= max.to_julian_days()); + assert_eq!(result.get_year(), 2020); + } + + #[test] + fn test_random_alphanumeric() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let result = RandomValueGenerator::generate_random_alphanumeric(10, &mut stream); + + assert_eq!(result.len(), 10); + for ch in result.chars() { + assert!(RandomValueGenerator::ALPHA_NUMERIC.contains(ch)); + } + } + + #[test] + fn test_random_digits() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let result = RandomValueGenerator::generate_random_digits(5, &mut stream); + + assert_eq!(result.len(), 5); + for ch in result.chars() { + assert!(ch.is_ascii_digit()); + } + } + + #[test] + fn test_random_boolean() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + + // Test with 0% probability - should always be false + let _result_never = RandomValueGenerator::generate_random_boolean(0.0, &mut stream); + // Note: This might not always be false due to floating point precision, but typically should be + + // Test with 100% probability - should always be true + let result_always = RandomValueGenerator::generate_random_boolean(1.0, &mut stream); + assert!(result_always); + } + + #[test] + fn test_weighted_random_index() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let weights = vec![10, 20, 30, 40]; + let result = RandomValueGenerator::generate_weighted_random_index(&weights, &mut stream); + + assert!(result < weights.len()); + } + + #[test] + fn test_random_string_custom_charset() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let charset = "ABC123"; + let result = RandomValueGenerator::generate_random_string(8, charset, &mut stream); + + assert_eq!(result.len(), 8); + for ch in result.chars() { + assert!(charset.contains(ch)); + } + } +} diff --git a/tpcdsgen/src/random/mod.rs b/tpcdsgen/src/random/mod.rs new file mode 100644 index 00000000..99b87508 --- /dev/null +++ b/tpcdsgen/src/random/mod.rs @@ -0,0 +1,5 @@ +pub mod generator; +pub mod stream; + +pub use generator::RandomValueGenerator; +pub use stream::{RandomNumberStream, RandomNumberStreamImpl}; diff --git a/tpcdsgen/src/random/stream.rs b/tpcdsgen/src/random/stream.rs new file mode 100644 index 00000000..eff8505b --- /dev/null +++ b/tpcdsgen/src/random/stream.rs @@ -0,0 +1,186 @@ +use crate::{check_argument, error::Result, TpcdsError}; + +pub trait RandomNumberStream: Send + Sync { + fn next_random(&mut self) -> i64; + fn next_random_double(&mut self) -> f64; + fn skip_rows(&mut self, number_of_rows: i64); + fn reset_seed(&mut self); + fn get_seeds_used(&self) -> i32; + fn reset_seeds_used(&mut self); + fn get_seeds_per_row(&self) -> i32; +} + +#[derive(Debug, Clone)] +pub struct RandomNumberStreamImpl { + // Constants matching Java implementation exactly + seed: i64, + initial_seed: i64, + seeds_used: i32, + seeds_per_row: i32, +} + +impl RandomNumberStreamImpl { + const DEFAULT_SEED_BASE: i32 = 19620718; + const MULTIPLIER: i64 = 16807; + const QUOTIENT: i64 = 127773; // the quotient MAX_INT / MULTIPLIER + const REMAINDER: i64 = 2836; // the remainder MAX_INT % MULTIPLIER + + pub fn new(seeds_per_row: i32) -> Result { + check_argument!(seeds_per_row >= 0, "seedsPerRow must be >=0"); + Ok(RandomNumberStreamImpl { + initial_seed: 3, + seed: 3, + seeds_used: 0, + seeds_per_row, + }) + } + + pub fn new_with_column(global_column_number: i32, seeds_per_row: i32) -> Result { + Self::new_with_base(global_column_number, Self::DEFAULT_SEED_BASE, seeds_per_row) + } + + pub fn new_with_base( + global_column_number: i32, + seed_base: i32, + seeds_per_row: i32, + ) -> Result { + check_argument!(seeds_per_row >= 0, "seedsPerRow must be >=0"); + let initial_seed = seed_base as i64 + global_column_number as i64 * (i32::MAX as i64 / 799); + Ok(RandomNumberStreamImpl { + initial_seed, + seed: initial_seed, + seeds_used: 0, + seeds_per_row, + }) + } +} + +impl RandomNumberStream for RandomNumberStreamImpl { + // https://en.wikipedia.org/wiki/Lehmer_random_number_generator + fn next_random(&mut self) -> i64 { + let mut next_seed = self.seed; + let division_result = next_seed / Self::QUOTIENT; + let mod_result = next_seed % Self::QUOTIENT; + next_seed = Self::MULTIPLIER * mod_result - division_result * Self::REMAINDER; + if next_seed < 0 { + next_seed += i32::MAX as i64; + } + + self.seed = next_seed; + self.seeds_used += 1; + self.seed + } + + fn next_random_double(&mut self) -> f64 { + self.next_random() as f64 / i32::MAX as f64 + } + + fn skip_rows(&mut self, number_of_rows: i64) { + let mut number_of_values_to_skip = number_of_rows * self.seeds_per_row as i64; + let mut next_seed = self.initial_seed; + let mut multiplier = Self::MULTIPLIER; + + while number_of_values_to_skip > 0 { + if number_of_values_to_skip % 2 != 0 { + // n is odd + next_seed = (multiplier * next_seed) % i32::MAX as i64; + } + number_of_values_to_skip /= 2; + multiplier = (multiplier * multiplier) % i32::MAX as i64; + } + + self.seed = next_seed; + self.seeds_used = 0; + } + + fn reset_seed(&mut self) { + self.seed = self.initial_seed; + self.seeds_used = 0; + } + + fn get_seeds_used(&self) -> i32 { + self.seeds_used + } + + fn reset_seeds_used(&mut self) { + self.seeds_used = 0; + } + + fn get_seeds_per_row(&self) -> i32 { + self.seeds_per_row + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_random_stream_creation() { + let stream = RandomNumberStreamImpl::new(1).unwrap(); + assert_eq!(stream.get_seeds_per_row(), 1); + assert_eq!(stream.get_seeds_used(), 0); + } + + #[test] + fn test_random_stream_with_column() { + let stream = RandomNumberStreamImpl::new_with_column(1, 1).unwrap(); + assert_eq!(stream.get_seeds_per_row(), 1); + + // Initial seed should be computed based on column number + assert_ne!(stream.initial_seed, 3); // Should be different from default + } + + #[test] + fn test_next_random() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let first = stream.next_random(); + let second = stream.next_random(); + + // Should generate different values + assert_ne!(first, second); + assert_eq!(stream.get_seeds_used(), 2); + } + + #[test] + fn test_random_double() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let random_double = stream.next_random_double(); + + // Should be between 0 and 1 + assert!(random_double >= 0.0 && random_double <= 1.0); + } + + #[test] + fn test_reset_seed() { + let mut stream = RandomNumberStreamImpl::new(1).unwrap(); + let initial = stream.next_random(); + stream.next_random(); // Generate another + + stream.reset_seed(); + let after_reset = stream.next_random(); + + assert_eq!(initial, after_reset); + assert_eq!(stream.get_seeds_used(), 1); + } + + #[test] + fn test_skip_rows() { + let mut stream1 = RandomNumberStreamImpl::new(2).unwrap(); + let mut stream2 = RandomNumberStreamImpl::new(2).unwrap(); + + // Generate 2 rows manually on stream1 + stream1.next_random(); + stream1.next_random(); + stream1.next_random(); + stream1.next_random(); + + // Skip 2 rows on stream2 + stream2.skip_rows(2); + + // Both should now generate the same next value + let manual = stream1.next_random(); + let skipped = stream2.next_random(); + assert_eq!(manual, skipped); + } +} diff --git a/tpcdsgen/src/row/abstract_row_generator.rs b/tpcdsgen/src/row/abstract_row_generator.rs new file mode 100644 index 00000000..bd409cee --- /dev/null +++ b/tpcdsgen/src/row/abstract_row_generator.rs @@ -0,0 +1,132 @@ +use crate::generator::GeneratorColumn; +use crate::random::{RandomNumberStream, RandomNumberStreamImpl}; +use crate::table::Table; + +/// Abstract base for row generators (AbstractRowGenerator) +/// Handles common functionality like random number stream management +pub struct AbstractRowGenerator { + table: Table, + /// Random number streams stored in a Vec, indexed by (global_column_number - base_global_column_number) + /// This replaces HashMap for O(1) direct array access without hashing overhead + random_number_streams: Vec, + /// The minimum global column number for this table's columns + /// Used to convert global_column_number to Vec index + base_global_column_number: i32, +} + +impl AbstractRowGenerator { + /// Create a new abstract row generator for the given table + /// Pre-creates all random number streams for the table's generator columns + /// (matching Java's AbstractRowGenerator constructor behavior) + pub fn new(table: Table) -> Self { + let column_count = table.get_generator_column_count(); + + // Find the base global column number (minimum among all columns) + let base_global_column_number = if column_count > 0 { + table + .get_generator_column_by_index(0) + .map(|col| col.get_global_column_number()) + .unwrap_or(0) + } else { + 0 + }; + + // Pre-create all streams for this table's generator columns + // This is critical because consume_remaining_seeds_for_row needs to advance + // ALL streams, even ones that haven't been accessed yet + let mut random_number_streams = Vec::with_capacity(column_count); + + for i in 0..column_count { + if let Some(gen_col) = table.get_generator_column_by_index(i) { + let global_column_number = gen_col.get_global_column_number(); + let seeds_per_row = gen_col.get_seeds_per_row(); + + let stream = + RandomNumberStreamImpl::new_with_column(global_column_number, seeds_per_row) + .expect("Failed to create random number stream"); + random_number_streams.push(stream); + } + } + + Self { + table, + random_number_streams, + base_global_column_number, + } + } + + /// Get the table this generator is for + pub fn get_table(&self) -> Table { + self.table + } + + /// Get a random number stream for a generator column + /// Uses direct array indexing for O(1) access + pub fn get_random_number_stream( + &mut self, + column: &dyn GeneratorColumn, + ) -> &mut dyn RandomNumberStream { + let global_column_number = column.get_global_column_number(); + let index = (global_column_number - self.base_global_column_number) as usize; + + &mut self.random_number_streams[index] + } + + /// Consume remaining seeds for all streams (AbstractRowGenerator.consumeRemainingSeedsForRow) + pub fn consume_remaining_seeds_for_row(&mut self) { + use crate::random::RandomValueGenerator; + + for stream in self.random_number_streams.iter_mut() { + // Consume remaining seeds until each stream has used its full seeds_per_row allocation + while stream.get_seeds_used() < stream.get_seeds_per_row() { + RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + } + // Reset seeds used count for next row + stream.reset_seeds_used(); + } + } + + /// Skip rows for all streams until reaching the starting row number + pub fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + for stream in self.random_number_streams.iter_mut() { + stream.skip_rows(starting_row_number); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::generator::CallCenterGeneratorColumn; + + #[test] + fn test_abstract_row_generator_creation() { + let generator = AbstractRowGenerator::new(Table::CallCenter); + assert_eq!(generator.get_table(), Table::CallCenter); + } + + #[test] + fn test_random_number_stream_creation() { + let mut generator = AbstractRowGenerator::new(Table::CallCenter); + let column = &CallCenterGeneratorColumn::CcCallCenterSk; + + let _stream1 = generator.get_random_number_stream(column); + let _stream2 = generator.get_random_number_stream(column); + + // Should reuse the same stream for the same column + assert_eq!(generator.random_number_streams.len(), 34); + } + + #[test] + fn test_multiple_column_streams() { + let mut generator = AbstractRowGenerator::new(Table::CallCenter); + let col1 = &CallCenterGeneratorColumn::CcCallCenterSk; + let col2 = &CallCenterGeneratorColumn::CcCallCenterId; + + let _stream1 = generator.get_random_number_stream(col1); + let _stream2 = generator.get_random_number_stream(col2); + + // Should create separate streams for different columns + assert_eq!(generator.random_number_streams.len(), 34); + } +} diff --git a/tpcdsgen/src/row/call_center_row.rs b/tpcdsgen/src/row/call_center_row.rs new file mode 100644 index 00000000..415c18c5 --- /dev/null +++ b/tpcdsgen/src/row/call_center_row.rs @@ -0,0 +1,490 @@ +use crate::row::TableRow; +use crate::types::{Address, Decimal}; + +/// Call Center row data structure (CallCenterRow) +/// Contains all fields for the CALL_CENTER table in TPC-DS +#[derive(Debug, Clone, PartialEq)] +pub struct CallCenterRow { + // Primary key + cc_call_center_sk: i64, + + // Business key and versioning + cc_call_center_id: String, + cc_rec_start_date_id: String, + cc_rec_end_date_id: String, + cc_closed_date_id: String, + cc_open_date_id: String, + + // Call center information + cc_name: String, + cc_class: String, + cc_employees: i32, + cc_sq_ft: i32, + cc_hours: String, + cc_manager: String, + + // Market information + cc_market_id: i32, + cc_market_class: String, + cc_market_desc: String, + cc_market_manager: String, + + // Organization hierarchy + cc_division_id: i32, + cc_division_name: String, + cc_company: i32, + cc_company_name: String, + + // Address information (embedded) + cc_address: Address, + + // Financial information + cc_tax_percentage: Decimal, + + // Null bitmap for handling null values + null_bit_map: i64, +} + +impl CallCenterRow { + /// Create a new builder for CallCenterRow + pub fn builder() -> CallCenterRowBuilder { + CallCenterRowBuilder::new() + } + + // Getter methods (matching Java implementation) + pub fn get_cc_call_center_sk(&self) -> i64 { + self.cc_call_center_sk + } + + pub fn get_cc_call_center_id(&self) -> &str { + &self.cc_call_center_id + } + + pub fn get_cc_rec_start_date_id(&self) -> &str { + &self.cc_rec_start_date_id + } + + pub fn get_cc_rec_end_date_id(&self) -> &str { + &self.cc_rec_end_date_id + } + + pub fn get_cc_closed_date_id(&self) -> &str { + &self.cc_closed_date_id + } + + pub fn get_cc_open_date_id(&self) -> &str { + &self.cc_open_date_id + } + + pub fn get_cc_name(&self) -> &str { + &self.cc_name + } + + pub fn get_cc_class(&self) -> &str { + &self.cc_class + } + + pub fn get_cc_employees(&self) -> i32 { + self.cc_employees + } + + pub fn get_cc_sq_ft(&self) -> i32 { + self.cc_sq_ft + } + + pub fn get_cc_hours(&self) -> &str { + &self.cc_hours + } + + pub fn get_cc_manager(&self) -> &str { + &self.cc_manager + } + + pub fn get_cc_market_id(&self) -> i32 { + self.cc_market_id + } + + pub fn get_cc_market_class(&self) -> &str { + &self.cc_market_class + } + + pub fn get_cc_market_desc(&self) -> &str { + &self.cc_market_desc + } + + pub fn get_cc_market_manager(&self) -> &str { + &self.cc_market_manager + } + + pub fn get_cc_division_id(&self) -> i32 { + self.cc_division_id + } + + pub fn get_cc_division_name(&self) -> &str { + &self.cc_division_name + } + + pub fn get_cc_company(&self) -> i32 { + self.cc_company + } + + pub fn get_cc_company_name(&self) -> &str { + &self.cc_company_name + } + + pub fn get_cc_address(&self) -> &Address { + &self.cc_address + } + + pub fn get_cc_tax_percentage(&self) -> &Decimal { + &self.cc_tax_percentage + } + + pub fn get_null_bit_map(&self) -> i64 { + self.null_bit_map + } + + /// Check if a field should be null based on the null bitmap + fn is_null(&self, column_position: i32) -> bool { + (self.null_bit_map & (1 << column_position)) != 0 + } + + /// Format a value as string, handling nulls + fn format_value(&self, value: &str, column_position: i32) -> String { + if self.is_null(column_position) { + "NULL".to_string() + } else { + value.to_string() + } + } + + /// Format a numeric value as string, handling nulls + fn format_numeric(&self, value: T, column_position: i32) -> String { + if self.is_null(column_position) { + "NULL".to_string() + } else { + value.to_string() + } + } +} + +impl TableRow for CallCenterRow { + /// Get all values as strings for CSV output (getValues()) + fn get_values(&self) -> Vec { + vec![ + self.format_numeric(self.cc_call_center_sk, 0), + self.format_value(&self.cc_call_center_id, 1), + self.format_value(&self.cc_rec_start_date_id, 2), + self.format_value(&self.cc_rec_end_date_id, 3), + self.format_value(&self.cc_closed_date_id, 4), + self.format_value(&self.cc_open_date_id, 5), + self.format_value(&self.cc_name, 6), + self.format_value(&self.cc_class, 7), + self.format_numeric(self.cc_employees, 8), + self.format_numeric(self.cc_sq_ft, 9), + self.format_value(&self.cc_hours, 10), + self.format_value(&self.cc_manager, 11), + self.format_numeric(self.cc_market_id, 12), + self.format_value(&self.cc_market_class, 13), + self.format_value(&self.cc_market_desc, 14), + self.format_value(&self.cc_market_manager, 15), + self.format_numeric(self.cc_division_id, 16), + self.format_value(&self.cc_division_name, 17), + self.format_numeric(self.cc_company, 18), + self.format_value(&self.cc_company_name, 19), + // Address fields (flattened) + self.format_numeric(self.cc_address.get_street_number(), 20), + self.format_value(&self.cc_address.get_street_name(), 21), + self.format_value(self.cc_address.get_street_type(), 22), + self.format_value(self.cc_address.get_suite_number(), 23), + self.format_value(self.cc_address.get_city(), 24), + self.format_value(self.cc_address.get_county().unwrap_or(""), 25), + self.format_value(self.cc_address.get_state(), 26), + self.format_numeric(self.cc_address.get_zip(), 27), + self.format_value(self.cc_address.get_country(), 28), + self.format_numeric(self.cc_address.get_gmt_offset(), 29), + self.format_value(&self.cc_tax_percentage.to_string(), 30), + ] + } +} + +/// Builder for CallCenterRow (CallCenterRow.Builder) +#[derive(Debug, Default)] +pub struct CallCenterRowBuilder { + cc_call_center_sk: Option, + cc_call_center_id: Option, + cc_rec_start_date_id: Option, + cc_rec_end_date_id: Option, + cc_closed_date_id: Option, + cc_open_date_id: Option, + cc_name: Option, + cc_class: Option, + cc_employees: Option, + cc_sq_ft: Option, + cc_hours: Option, + cc_manager: Option, + cc_market_id: Option, + cc_market_class: Option, + cc_market_desc: Option, + cc_market_manager: Option, + cc_division_id: Option, + cc_division_name: Option, + cc_company: Option, + cc_company_name: Option, + cc_address: Option
, + cc_tax_percentage: Option, + null_bit_map: Option, +} + +impl CallCenterRowBuilder { + pub fn new() -> Self { + Self::default() + } + + // Setter methods (matching Java builder pattern) + pub fn set_cc_call_center_sk(mut self, value: i64) -> Self { + self.cc_call_center_sk = Some(value); + self + } + + pub fn set_cc_call_center_id(mut self, value: String) -> Self { + self.cc_call_center_id = Some(value); + self + } + + pub fn set_cc_rec_start_date_id(mut self, value: String) -> Self { + self.cc_rec_start_date_id = Some(value); + self + } + + pub fn set_cc_rec_end_date_id(mut self, value: String) -> Self { + self.cc_rec_end_date_id = Some(value); + self + } + + pub fn set_cc_closed_date_id(mut self, value: String) -> Self { + self.cc_closed_date_id = Some(value); + self + } + + pub fn set_cc_open_date_id(mut self, value: String) -> Self { + self.cc_open_date_id = Some(value); + self + } + + pub fn set_cc_name(mut self, value: String) -> Self { + self.cc_name = Some(value); + self + } + + pub fn set_cc_class(mut self, value: String) -> Self { + self.cc_class = Some(value); + self + } + + pub fn set_cc_employees(mut self, value: i32) -> Self { + self.cc_employees = Some(value); + self + } + + pub fn set_cc_sq_ft(mut self, value: i32) -> Self { + self.cc_sq_ft = Some(value); + self + } + + pub fn set_cc_hours(mut self, value: String) -> Self { + self.cc_hours = Some(value); + self + } + + pub fn set_cc_manager(mut self, value: String) -> Self { + self.cc_manager = Some(value); + self + } + + pub fn set_cc_market_id(mut self, value: i32) -> Self { + self.cc_market_id = Some(value); + self + } + + pub fn set_cc_market_class(mut self, value: String) -> Self { + self.cc_market_class = Some(value); + self + } + + pub fn set_cc_market_desc(mut self, value: String) -> Self { + self.cc_market_desc = Some(value); + self + } + + pub fn set_cc_market_manager(mut self, value: String) -> Self { + self.cc_market_manager = Some(value); + self + } + + pub fn set_cc_division_id(mut self, value: i32) -> Self { + self.cc_division_id = Some(value); + self + } + + pub fn set_cc_division_name(mut self, value: String) -> Self { + self.cc_division_name = Some(value); + self + } + + pub fn set_cc_company(mut self, value: i32) -> Self { + self.cc_company = Some(value); + self + } + + pub fn set_cc_company_name(mut self, value: String) -> Self { + self.cc_company_name = Some(value); + self + } + + pub fn set_cc_address(mut self, value: Address) -> Self { + self.cc_address = Some(value); + self + } + + pub fn set_cc_tax_percentage(mut self, value: Decimal) -> Self { + self.cc_tax_percentage = Some(value); + self + } + + pub fn set_null_bit_map(mut self, value: i64) -> Self { + self.null_bit_map = Some(value); + self + } + + /// Build the CallCenterRow + pub fn build(self) -> CallCenterRow { + CallCenterRow { + cc_call_center_sk: self.cc_call_center_sk.unwrap_or(0), + cc_call_center_id: self.cc_call_center_id.unwrap_or_default(), + cc_rec_start_date_id: self.cc_rec_start_date_id.unwrap_or_default(), + cc_rec_end_date_id: self.cc_rec_end_date_id.unwrap_or_default(), + cc_closed_date_id: self.cc_closed_date_id.unwrap_or_default(), // Default empty for null + cc_open_date_id: self.cc_open_date_id.unwrap_or_default(), + cc_name: self.cc_name.unwrap_or_default(), + cc_class: self.cc_class.unwrap_or_default(), + cc_employees: self.cc_employees.unwrap_or(0), + cc_sq_ft: self.cc_sq_ft.unwrap_or(0), + cc_hours: self.cc_hours.unwrap_or_default(), + cc_manager: self.cc_manager.unwrap_or_default(), + cc_market_id: self.cc_market_id.unwrap_or(0), + cc_market_class: self.cc_market_class.unwrap_or_default(), + cc_market_desc: self.cc_market_desc.unwrap_or_default(), + cc_market_manager: self.cc_market_manager.unwrap_or_default(), + cc_division_id: self.cc_division_id.unwrap_or(0), + cc_division_name: self.cc_division_name.unwrap_or_default(), + cc_company: self.cc_company.unwrap_or(0), + cc_company_name: self.cc_company_name.unwrap_or_default(), + cc_address: self.cc_address.unwrap_or_default(), + cc_tax_percentage: self.cc_tax_percentage.unwrap_or_default(), + null_bit_map: self.null_bit_map.unwrap_or(0), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{Address, Decimal}; + + #[test] + fn test_call_center_row_builder() { + let address = Address::builder() + .street_number(123) + .street_name("Main St".to_string()) + .street_type("St".to_string()) + .suite_number("Suite 100".to_string()) + .city("Seattle".to_string()) + .county("King".to_string()) + .state("WA".to_string()) + .zip(98101) + .country("United States".to_string()) + .gmt_offset(-8) + .build(); + + let tax_percentage = Decimal::new(825, 2).unwrap(); // 8.25% + + let row = CallCenterRow::builder() + .set_cc_call_center_sk(1) + .set_cc_call_center_id("AAAAAAAABAAAAAAA".to_string()) + .set_cc_rec_start_date_id(2450815.to_string()) + .set_cc_rec_end_date_id(2451179.to_string()) + .set_cc_closed_date_id((-1).to_string()) + .set_cc_open_date_id(2450816.to_string()) + .set_cc_name("NY Metro".to_string()) + .set_cc_class("large".to_string()) + .set_cc_employees(2) + .set_cc_sq_ft(1138) + .set_cc_hours("8AM-8AM".to_string()) + .set_cc_manager("Bob Belcher".to_string()) + .set_cc_market_id(6) + .set_cc_market_class("More than other authori".to_string()) + .set_cc_market_desc("Enough employees over the".to_string()) + .set_cc_market_manager("Julius Tran".to_string()) + .set_cc_division_id(3) + .set_cc_division_name("pri".to_string()) + .set_cc_company(6) + .set_cc_company_name("cally".to_string()) + .set_cc_address(address) + .set_cc_tax_percentage(tax_percentage) + .set_null_bit_map(0) + .build(); + + // Test getters + assert_eq!(row.get_cc_call_center_sk(), 1); + assert_eq!(row.get_cc_call_center_id(), "AAAAAAAABAAAAAAA"); + assert_eq!(row.get_cc_name(), "NY Metro"); + assert_eq!(row.get_cc_employees(), 2); + assert_eq!(row.get_cc_sq_ft(), 1138); + assert_eq!(row.get_cc_tax_percentage().to_string(), "8.25"); + } + + #[test] + fn test_call_center_row_table_row() { + let row = CallCenterRow::builder() + .set_cc_call_center_sk(1) + .set_cc_call_center_id("TEST123".to_string()) + .set_cc_name("Test Center".to_string()) + .build(); + + let values = row.get_values(); + assert_eq!(values.len(), 31); // 31 columns total + assert_eq!(values[0], "1"); // cc_call_center_sk + assert_eq!(values[1], "TEST123"); // cc_call_center_id + assert_eq!(values[6], "Test Center"); // cc_name + } + + #[test] + fn test_call_center_row_clone_and_equality() { + let row1 = CallCenterRow::builder() + .set_cc_call_center_sk(42) + .set_cc_name("Test Center".to_string()) + .build(); + + let row2 = row1.clone(); + assert_eq!(row1, row2); + assert_eq!(row1.get_cc_call_center_sk(), row2.get_cc_call_center_sk()); + assert_eq!(row1.get_cc_name(), row2.get_cc_name()); + } + + #[test] + fn test_builder_chaining() { + // Test that builder methods can be chained in any order + let row = CallCenterRow::builder() + .set_cc_employees(100) + .set_cc_call_center_sk(5) + .set_cc_name("Chained Builder Test".to_string()) + .set_cc_sq_ft(5000) + .build(); + + assert_eq!(row.get_cc_call_center_sk(), 5); + assert_eq!(row.get_cc_name(), "Chained Builder Test"); + assert_eq!(row.get_cc_employees(), 100); + assert_eq!(row.get_cc_sq_ft(), 5000); + } +} diff --git a/tpcdsgen/src/row/call_center_row_generator.rs b/tpcdsgen/src/row/call_center_row_generator.rs new file mode 100644 index 00000000..e1f57c1d --- /dev/null +++ b/tpcdsgen/src/row/call_center_row_generator.rs @@ -0,0 +1,458 @@ +use crate::config::Session; +use crate::distribution::{CallCenterDistributions, FirstNamesWeights, NamesDistributions}; +use crate::error::Result; +use crate::generator::CallCenterGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, CallCenterRow, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::{ + compute_scd_key, get_value_for_slowly_changing_dimension, SlowlyChangingDimensionKey, +}; +use crate::table::Table; +use crate::types::{Address, Date, Decimal}; + +/// Row generator for the CALL_CENTER table (CallCenterRowGenerator) +pub struct CallCenterRowGenerator { + abstract_generator: AbstractRowGenerator, + previous_row: Option, +} + +// Constants matching Java implementation +// We'll define these as functions since const Decimal isn't available +fn min_tax_percentage() -> Decimal { + Decimal::new(0, 2).unwrap() +} + +fn max_tax_percentage() -> Decimal { + Decimal::new(12, 2).unwrap() +} +const WIDTH_CC_DIVISION_NAME: i32 = 50; +const WIDTH_CC_MARKET_CLASS: i32 = 50; +const WIDTH_CC_MARKET_DESC: i32 = 100; +const MAX_NUMBER_OF_EMPLOYEES_UNSCALED: i32 = 7; +const JULIAN_DATE_START: i64 = Date::JULIAN_DATA_START_DATE - 23; // 23 is the ordinal of CALL_CENTER table + +impl Default for CallCenterRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl CallCenterRowGenerator { + /// Create a new CallCenterRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::CallCenter), + previous_row: None, + } + } + + /// Generate a CallCenterRow with realistic data following Java implementation + fn generate_call_center_row( + &mut self, + row_number: i64, + session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcNulls); + let _threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let _bit_map = RandomValueGenerator::generate_uniform_random_int(1, i32::MAX, nulls_stream); + + // The id combined with start and end dates represent the unique key for this row. + // The id is what would be a primary key if there were only one version of each row + // the start and end dates are the version information for the row. + let scd_key: SlowlyChangingDimensionKey = compute_scd_key(Table::CallCenter, row_number); + + let end_date_str = if scd_key.get_end_date() == -1 { + String::new() // Empty string for null end dates + } else { + Date::julian_to_date_string(scd_key.get_end_date()) + }; + + let scaling = session.get_scaling(); + let is_new_business_key = scd_key.is_new_business_key(); + + // These fields only change when there is a new id. They remain constant across different version of a row. + let (cc_open_date_id, cc_name, cc_address) = if is_new_business_key { + let open_date_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcOpenDateId); + let open_date_random = + RandomValueGenerator::generate_uniform_random_int(-365, 0, open_date_stream); + let open_date_julian = JULIAN_DATE_START - open_date_random as i64; + let open_date_id = open_date_julian.to_string(); + + let number_of_call_centers = + CallCenterDistributions::get_number_of_call_centers().unwrap_or(12); + let suffix = (row_number / number_of_call_centers as i64) as i32; + let cc_name = CallCenterDistributions::get_call_center_at_index( + (row_number % number_of_call_centers as i64) as usize, + ) + .unwrap_or("Unknown"); + + let final_cc_name = if suffix > 0 { + format!("{}_{}", cc_name, suffix) + } else { + cc_name.to_string() + }; + + // Generate address + let address_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcAddress); + let address = + Address::make_address_for_column(Table::CallCenter, address_stream, scaling)?; + + (open_date_id, final_cc_name, address) + } else { + // Use values from previous row - DO NOT consume random streams! + if let Some(ref prev_row) = self.previous_row { + ( + prev_row.get_cc_open_date_id().to_string(), + prev_row.get_cc_name().to_string(), + prev_row.get_cc_address().clone(), + ) + } else { + return Err(crate::TpcdsError::new( + "previousRow has not yet been initialized", + )); + } + }; + + // Select the random number that controls if a field changes from one record to the next. + let scd_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcScd); + let mut field_change_flag = scd_stream.next_random() as i32; + + // The rest of the fields can either be a new data value or not. + // We use a random number to determine which fields to replace and which to retain. + // A field changes if is_new_business_key is true or the lowest order bit of the random number is zero. + // Then we divide the field_change_flag by 2 so the next field is determined by the next lower bit. + + // There is a bug in the C code for adjusting pointer types (which this is) with slowly changing dimensions, + // so it always uses the new value + let class_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcClass); + let cc_class = + CallCenterDistributions::pick_random_call_center_class(class_stream).unwrap_or("large"); + field_change_flag >>= 1; + + let employees_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcEmployees); + let mut cc_employees = RandomValueGenerator::generate_uniform_random_int( + 1, + MAX_NUMBER_OF_EMPLOYEES_UNSCALED + * scaling.get_scale().ceil() as i32 + * scaling.get_scale().ceil() as i32, + employees_stream, + ); + if let Some(ref prev_row) = self.previous_row { + cc_employees = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_employees(), + cc_employees, + ); + } + field_change_flag >>= 1; + + let sq_ft_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcSqFt); + let mut cc_sq_ft = + RandomValueGenerator::generate_uniform_random_int(100, 700, sq_ft_stream) + * cc_employees; + if let Some(ref prev_row) = self.previous_row { + cc_sq_ft = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_sq_ft(), + cc_sq_ft, + ); + } + field_change_flag >>= 1; + + // Another casualty of the bug with pointer types in the C code. Will always use a new value. + let hours_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcHours); + let cc_hours = CallCenterDistributions::pick_random_call_center_hours(hours_stream) + .unwrap_or("8AM-8PM"); + field_change_flag >>= 1; + + let manager_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcManager); + let manager_first_name = NamesDistributions::pick_random_first_name( + if session.is_sexist() { + FirstNamesWeights::MaleFrequency + } else { + FirstNamesWeights::GeneralFrequency + }, + manager_stream, + ) + .unwrap_or("John"); + let manager_last_name = + NamesDistributions::pick_random_last_name(manager_stream).unwrap_or("Smith"); + let mut cc_manager = format!("{} {}", manager_first_name, manager_last_name); + if let Some(ref prev_row) = self.previous_row { + cc_manager = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_manager().to_string(), + cc_manager, + ); + } + field_change_flag >>= 1; + + let market_id_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcMarketId); + let mut cc_market_id = + RandomValueGenerator::generate_uniform_random_int(1, 6, market_id_stream); + if let Some(ref prev_row) = self.previous_row { + cc_market_id = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_market_id(), + cc_market_id, + ); + } + field_change_flag >>= 1; + + let market_class_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcMarketClass); + let mut cc_market_class = RandomValueGenerator::generate_random_text( + 20, + WIDTH_CC_MARKET_CLASS, + market_class_stream, + ); + if let Some(ref prev_row) = self.previous_row { + cc_market_class = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_market_class().to_string(), + cc_market_class, + ); + } + field_change_flag >>= 1; + + let market_desc_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcMarketDesc); + let mut cc_market_desc = RandomValueGenerator::generate_random_text( + 20, + WIDTH_CC_MARKET_DESC, + market_desc_stream, + ); + if let Some(ref prev_row) = self.previous_row { + cc_market_desc = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_market_desc().to_string(), + cc_market_desc, + ); + } + field_change_flag >>= 1; + + let market_manager_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcMarketManager); + let market_manager_first_name = NamesDistributions::pick_random_first_name( + if session.is_sexist() { + FirstNamesWeights::MaleFrequency + } else { + FirstNamesWeights::GeneralFrequency + }, + market_manager_stream, + ) + .unwrap_or("Jane"); + let market_manager_last_name = + NamesDistributions::pick_random_last_name(market_manager_stream).unwrap_or("Doe"); + let mut cc_market_manager = + format!("{} {}", market_manager_first_name, market_manager_last_name); + if let Some(ref prev_row) = self.previous_row { + cc_market_manager = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_market_manager().to_string(), + cc_market_manager, + ); + } + field_change_flag >>= 1; + + let company_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcCompany); + let mut cc_company = + RandomValueGenerator::generate_uniform_random_int(1, 6, company_stream); + if let Some(ref prev_row) = self.previous_row { + cc_company = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_company(), + cc_company, + ); + } + field_change_flag >>= 1; + + let division_id_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcCompany); // Note: uses same stream as company + let mut cc_division_id = + RandomValueGenerator::generate_uniform_random_int(1, 6, division_id_stream); + if let Some(ref prev_row) = self.previous_row { + cc_division_id = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_division_id(), + cc_division_id, + ); + } + field_change_flag >>= 1; + + // Generate word doesn't use random numbers - it deterministically creates from seed + // We still need to consume the stream to maintain RNG state + let _division_name_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcDivisionName); + let mut cc_division_name = RandomValueGenerator::generate_word( + cc_division_id as i64, + WIDTH_CC_DIVISION_NAME, + crate::distribution::get_syllables_distribution(), + ); + if let Some(ref prev_row) = self.previous_row { + cc_division_name = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_division_name().to_string(), + cc_division_name, + ); + } + field_change_flag >>= 1; + + let _company_name_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcCompanyName); + let mut cc_company_name = RandomValueGenerator::generate_word( + cc_company as i64, + 10, + crate::distribution::get_syllables_distribution(), + ); + if let Some(ref prev_row) = self.previous_row { + cc_company_name = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + prev_row.get_cc_company_name().to_string(), + cc_company_name, + ); + } + field_change_flag >>= 1; + + let tax_percentage_stream = self + .abstract_generator + .get_random_number_stream(&CallCenterGeneratorColumn::CcTaxPercentage); + let mut cc_tax_percentage = RandomValueGenerator::generate_uniform_random_decimal( + min_tax_percentage(), + max_tax_percentage(), + tax_percentage_stream, + ); + if let Some(ref prev_row) = self.previous_row { + cc_tax_percentage = get_value_for_slowly_changing_dimension( + field_change_flag, + is_new_business_key, + *prev_row.get_cc_tax_percentage(), + cc_tax_percentage, + ); + } + + // Build the row in one go + let new_row = CallCenterRow::builder() + .set_null_bit_map(0) + .set_cc_call_center_sk(row_number) + .set_cc_call_center_id(scd_key.get_business_key().to_string()) + .set_cc_rec_start_date_id(Date::julian_to_date_string(scd_key.get_start_date())) + .set_cc_rec_end_date_id(end_date_str) + .set_cc_closed_date_id(String::new()) + .set_cc_open_date_id(cc_open_date_id) + .set_cc_name(cc_name) + .set_cc_class(cc_class.to_string()) + .set_cc_employees(cc_employees) + .set_cc_sq_ft(cc_sq_ft) + .set_cc_hours(cc_hours.to_string()) + .set_cc_manager(cc_manager) + .set_cc_market_id(cc_market_id) + .set_cc_market_class(cc_market_class) + .set_cc_market_desc(cc_market_desc) + .set_cc_market_manager(cc_market_manager) + .set_cc_division_id(cc_division_id) + .set_cc_division_name(cc_division_name) + .set_cc_company(cc_company) + .set_cc_company_name(cc_company_name) + .set_cc_address(cc_address) + .set_cc_tax_percentage(cc_tax_percentage) + .build(); + + self.previous_row = Some(new_row.clone()); + + Ok(new_row) + } +} + +impl RowGenerator for CallCenterRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_call_center_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::Session; + use crate::row::TableRow; + + #[test] + fn test_call_center_row_generator_creation() { + let generator = CallCenterRowGenerator::new(); + assert_eq!(generator.abstract_generator.get_table(), Table::CallCenter); + } + + #[test] + fn test_generate_call_center_row() { + let mut generator = CallCenterRowGenerator::new(); + let session = Session::get_default_session(); + + let result = generator + .generate_row_and_child_rows(1, &session, None, None) + .unwrap(); + let rows = result.get_rows(); + + assert_eq!(rows.len(), 1); + assert!(result.should_end_row()); + + // Check that we can get values (CSV serialization works) + let values = rows[0].get_values(); + assert_eq!(values[0], "1"); // cc_call_center_sk should be row number + } +} diff --git a/tpcdsgen/src/row/catalog_page_row.rs b/tpcdsgen/src/row/catalog_page_row.rs new file mode 100644 index 00000000..fe3f6753 --- /dev/null +++ b/tpcdsgen/src/row/catalog_page_row.rs @@ -0,0 +1,113 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog page row structure and formatting + +use crate::generator::{CatalogPageGeneratorColumn, GeneratorColumn}; +use crate::row::TableRow; + +/// Catalog page row +#[derive(Clone)] +pub struct CatalogPageRow { + null_bit_map: i64, + cp_catalog_page_sk: i64, + cp_catalog_page_id: String, + cp_start_date_id: i64, + cp_end_date_id: i64, + cp_department: String, + cp_catalog_number: i32, + cp_catalog_page_number: i32, + cp_description: String, + cp_type: String, +} + +impl CatalogPageRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + cp_catalog_page_sk: i64, + cp_catalog_page_id: String, + cp_start_date_id: i64, + cp_end_date_id: i64, + cp_department: String, + cp_catalog_number: i32, + cp_catalog_page_number: i32, + cp_description: String, + cp_type: String, + ) -> Self { + CatalogPageRow { + null_bit_map, + cp_catalog_page_sk, + cp_catalog_page_id, + cp_start_date_id, + cp_end_date_id, + cp_department, + cp_catalog_number, + cp_catalog_page_number, + cp_description, + cp_type, + } + } + + fn is_null(&self, column: &CatalogPageGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - CatalogPageGeneratorColumn::CpCatalogPageSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key( + &self, + value: i64, + column: &CatalogPageGeneratorColumn, + ) -> String { + if self.is_null(column) || value < 0 { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null(&self, value: &str, column: &CatalogPageGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_int_or_null(&self, value: i32, column: &CatalogPageGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for CatalogPageRow { + fn get_values(&self) -> Vec { + use CatalogPageGeneratorColumn::*; + vec![ + self.get_string_or_null_for_key(self.cp_catalog_page_sk, &CpCatalogPageSk), + self.get_string_or_null(&self.cp_catalog_page_id, &CpCatalogPageId), + self.get_string_or_null_for_key(self.cp_start_date_id, &CpStartDateId), + self.get_string_or_null_for_key(self.cp_end_date_id, &CpEndDateId), + self.get_string_or_null(&self.cp_department, &CpDepartment), + self.get_int_or_null(self.cp_catalog_number, &CpCatalogNumber), + self.get_int_or_null(self.cp_catalog_page_number, &CpCatalogPageNumber), + self.get_string_or_null(&self.cp_description, &CpDescription), + self.get_string_or_null(&self.cp_type, &CpType), + ] + } +} diff --git a/tpcdsgen/src/row/catalog_page_row_generator.rs b/tpcdsgen/src/row/catalog_page_row_generator.rs new file mode 100644 index 00000000..859e48cf --- /dev/null +++ b/tpcdsgen/src/row/catalog_page_row_generator.rs @@ -0,0 +1,149 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog page row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::CatalogPageGeneratorColumn; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::catalog_page_row::CatalogPageRow; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::Date; + +const CATALOGS_PER_YEAR: i32 = 18; +const WIDTH_CP_DESCRIPTION: i32 = 100; + +pub struct CatalogPageRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl CatalogPageRowGenerator { + pub fn new() -> Self { + CatalogPageRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::CatalogPage), + } + } + + fn generate_catalog_page_row( + &mut self, + row_number: i64, + session: &Session, + ) -> Result { + use CatalogPageGeneratorColumn::*; + + let cp_catalog_page_sk = row_number; + let cp_department = "DEPARTMENT".to_string(); + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&CpNulls); + let null_bit_map = create_null_bit_map(Table::CatalogPage, stream); + + // Generate business key + let cp_catalog_page_id = crate::business_key_generator::make_business_key(row_number); + + // Calculate catalog page numbers + let row_count = session + .get_scaling() + .get_row_count(crate::config::table::Table::CatalogPage); + let catalog_page_max = ((row_count / CATALOGS_PER_YEAR as i64) as i32) + / (Date::DATE_MAXIMUM.year() - Date::DATE_MINIMUM.year() + 2); + let cp_catalog_number = ((row_number - 1) / catalog_page_max as i64 + 1) as i32; + let cp_catalog_page_number = ((row_number - 1) % catalog_page_max as i64 + 1) as i32; + + // Calculate catalog interval and type + let catalog_interval = (cp_catalog_number - 1) % CATALOGS_PER_YEAR; + let (cp_type, duration, offset) = match catalog_interval { + 0 | 1 => { + // bi-annual + let duration = 182; + let offset = catalog_interval * duration; + ("bi-annual", duration, offset) + } + 2..=5 => { + // quarterly (Q1-Q4) + let duration = 91; + let offset = (catalog_interval - 2) * duration; + ("quarterly", duration, offset) + } + _ => { + // monthly + let duration = 30; + let offset = (catalog_interval - 6) * duration; + ("monthly", duration, offset) + } + }; + + // Calculate start and end dates + let cp_start_date_id = Date::JULIAN_DATA_START_DATE + + offset as i64 + + ((cp_catalog_number - 1) / CATALOGS_PER_YEAR) as i64 * 365; + let cp_end_date_id = cp_start_date_id + duration as i64 - 1; + + // Generate description + let stream = self + .abstract_generator + .get_random_number_stream(&CpDescription); + let cp_description = RandomValueGenerator::generate_random_text( + WIDTH_CP_DESCRIPTION / 2, + WIDTH_CP_DESCRIPTION - 1, + stream, + ); + + let row = CatalogPageRow::new( + null_bit_map, + cp_catalog_page_sk, + cp_catalog_page_id, + cp_start_date_id, + cp_end_date_id, + cp_department, + cp_catalog_number, + cp_catalog_page_number, + cp_description, + cp_type.to_string(), + ); + + Ok(row) + } +} + +impl Default for CatalogPageRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for CatalogPageRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_catalog_page_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/catalog_returns_row.rs b/tpcdsgen/src/row/catalog_returns_row.rs new file mode 100644 index 00000000..dac42806 --- /dev/null +++ b/tpcdsgen/src/row/catalog_returns_row.rs @@ -0,0 +1,166 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog returns row structure + +use crate::generator::CatalogReturnsGeneratorColumn; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row structure for catalog_returns table +#[derive(Debug, Clone)] +pub struct CatalogReturnsRow { + null_bit_map: i64, + cr_returned_date_sk: i64, + cr_returned_time_sk: i64, + cr_item_sk: i64, + cr_refunded_customer_sk: i64, + cr_refunded_cdemo_sk: i64, + cr_refunded_hdemo_sk: i64, + cr_refunded_addr_sk: i64, + cr_returning_customer_sk: i64, + cr_returning_cdemo_sk: i64, + cr_returning_hdemo_sk: i64, + cr_returning_addr_sk: i64, + cr_call_center_sk: i64, + cr_catalog_page_sk: i64, + cr_ship_mode_sk: i64, + cr_warehouse_sk: i64, + cr_reason_sk: i64, + cr_order_number: i64, + cr_pricing: Pricing, +} + +impl CatalogReturnsRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + cr_returned_date_sk: i64, + cr_returned_time_sk: i64, + cr_item_sk: i64, + cr_refunded_customer_sk: i64, + cr_refunded_cdemo_sk: i64, + cr_refunded_hdemo_sk: i64, + cr_refunded_addr_sk: i64, + cr_returning_customer_sk: i64, + cr_returning_cdemo_sk: i64, + cr_returning_hdemo_sk: i64, + cr_returning_addr_sk: i64, + cr_call_center_sk: i64, + cr_catalog_page_sk: i64, + cr_ship_mode_sk: i64, + cr_warehouse_sk: i64, + cr_reason_sk: i64, + cr_order_number: i64, + cr_pricing: Pricing, + ) -> Self { + CatalogReturnsRow { + null_bit_map, + cr_returned_date_sk, + cr_returned_time_sk, + cr_item_sk, + cr_refunded_customer_sk, + cr_refunded_cdemo_sk, + cr_refunded_hdemo_sk, + cr_refunded_addr_sk, + cr_returning_customer_sk, + cr_returning_cdemo_sk, + cr_returning_hdemo_sk, + cr_returning_addr_sk, + cr_call_center_sk, + cr_catalog_page_sk, + cr_ship_mode_sk, + cr_warehouse_sk, + cr_reason_sk, + cr_order_number, + cr_pricing, + } + } + + fn is_null(&self, column: &CatalogReturnsGeneratorColumn) -> bool { + let column_number = column.get_global_column_number(); + let first_column = + CatalogReturnsGeneratorColumn::CrReturnedDateSk.get_global_column_number(); + let bit_position = column_number - first_column; + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key( + &self, + value: i64, + column: &CatalogReturnsGeneratorColumn, + ) -> String { + if self.is_null(column) || value < 0 { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null( + &self, + value: T, + column: &CatalogReturnsGeneratorColumn, + ) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for CatalogReturnsRow { + fn get_values(&self) -> Vec { + use CatalogReturnsGeneratorColumn::*; + + vec![ + self.get_string_or_null_for_key(self.cr_returned_date_sk, &CrReturnedDateSk), + self.get_string_or_null_for_key(self.cr_returned_time_sk, &CrReturnedTimeSk), + self.get_string_or_null_for_key(self.cr_item_sk, &CrItemSk), + self.get_string_or_null_for_key(self.cr_refunded_customer_sk, &CrRefundedCustomerSk), + self.get_string_or_null_for_key(self.cr_refunded_cdemo_sk, &CrRefundedCdemoSk), + self.get_string_or_null_for_key(self.cr_refunded_hdemo_sk, &CrRefundedHdemoSk), + self.get_string_or_null_for_key(self.cr_refunded_addr_sk, &CrRefundedAddrSk), + self.get_string_or_null_for_key(self.cr_returning_customer_sk, &CrReturningCustomerSk), + self.get_string_or_null_for_key(self.cr_returning_cdemo_sk, &CrReturningCdemoSk), + self.get_string_or_null_for_key(self.cr_returning_hdemo_sk, &CrReturningHdemoSk), + self.get_string_or_null_for_key(self.cr_returning_addr_sk, &CrReturningAddrSk), + self.get_string_or_null_for_key(self.cr_call_center_sk, &CrCallCenterSk), + self.get_string_or_null_for_key(self.cr_catalog_page_sk, &CrCatalogPageSk), + self.get_string_or_null_for_key(self.cr_ship_mode_sk, &CrShipModeSk), + self.get_string_or_null_for_key(self.cr_warehouse_sk, &CrWarehouseSk), + self.get_string_or_null_for_key(self.cr_reason_sk, &CrReasonSk), + self.get_string_or_null(self.cr_order_number, &CrOrderNumber), + self.get_string_or_null(self.cr_pricing.get_quantity(), &CrPricingQuantity), + self.get_string_or_null(self.cr_pricing.get_net_paid(), &CrPricingNetPaid), + self.get_string_or_null(self.cr_pricing.get_ext_tax(), &CrPricingExtTax), + self.get_string_or_null( + self.cr_pricing.get_net_paid_including_tax(), + &CrPricingNetPaidIncTax, + ), + self.get_string_or_null(self.cr_pricing.get_fee(), &CrPricingFee), + self.get_string_or_null(self.cr_pricing.get_ext_ship_cost(), &CrPricingExtShipCost), + self.get_string_or_null(self.cr_pricing.get_refunded_cash(), &CrPricingRefundedCash), + self.get_string_or_null( + self.cr_pricing.get_reversed_charge(), + &CrPricingReversedCharge, + ), + self.get_string_or_null(self.cr_pricing.get_store_credit(), &CrPricingStoreCredit), + self.get_string_or_null(self.cr_pricing.get_net_loss(), &CrPricingNetLoss), + ] + } +} + +use crate::generator::GeneratorColumn; diff --git a/tpcdsgen/src/row/catalog_returns_row_generator.rs b/tpcdsgen/src/row/catalog_returns_row_generator.rs new file mode 100644 index 00000000..ec057b28 --- /dev/null +++ b/tpcdsgen/src/row/catalog_returns_row_generator.rs @@ -0,0 +1,254 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog returns row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::CatalogReturnsGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::catalog_returns_row::CatalogReturnsRow; +use crate::row::catalog_sales_row::CatalogSalesRow; +use crate::row::{AbstractRowGenerator, GeneratedRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::generate_pricing_for_returns_table; + +/// Percentage of sales that get returned (same as store returns) +pub const RETURN_PERCENT: i32 = 10; + +/// Percentage of returns where the ship customer returns (vs bill customer) +const GIFT_PERCENTAGE: i32 = 10; + +pub struct CatalogReturnsRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl CatalogReturnsRowGenerator { + pub fn new() -> Self { + CatalogReturnsRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::CatalogReturns), + } + } + + /// Generate a return row from a sales row + /// This is called by CatalogSalesRowGenerator when a sale is returned + pub fn generate_row( + &mut self, + session: &Session, + sales_row: &CatalogSalesRow, + ) -> Result { + use CatalogReturnsGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&CrNulls); + let null_bit_map = create_null_bit_map(Table::CatalogReturns, stream); + + // Some fields are conditionally taken from the sale + // By default, use bill customer info (which gets refunded) + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturningCustomerSk); + let mut cr_returning_customer_sk = generate_join_key( + &CrReturningCustomerSk, + stream, + crate::config::Table::Customer, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturningCdemoSk); + let mut cr_returning_cdemo_sk = generate_join_key( + &CrReturningCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturningHdemoSk); + let cr_returning_hdemo_sk = generate_join_key( + &CrReturningHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturningAddrSk); + let mut cr_returning_addr_sk = generate_join_key( + &CrReturningAddrSk, + stream, + crate::config::Table::CustomerAddress, + 2, + scaling, + )?; + + // If the order was a gift (10%), the ship customer is doing the return + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturningCustomerSk); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + if random_int < GIFT_PERCENTAGE { + cr_returning_customer_sk = sales_row.get_cs_ship_customer_sk(); + cr_returning_cdemo_sk = sales_row.get_cs_ship_cdemo_sk(); + // skip cr_returning_hdemo_sk, since it doesn't exist on the sales record + cr_returning_addr_sk = sales_row.get_cs_ship_addr_sk(); + } + + // Generate return quantity (1 to original sale quantity) + let sales_pricing = sales_row.get_cs_pricing(); + let quantity = if sales_pricing.get_quantity() == -1 { + sales_pricing.get_quantity() + } else { + let stream = self.abstract_generator.get_random_number_stream(&CrPricing); + RandomValueGenerator::generate_uniform_random_int( + 1, + sales_pricing.get_quantity(), + stream, + ) + }; + + // Generate return pricing + let stream = self.abstract_generator.get_random_number_stream(&CrPricing); + let cr_pricing = generate_pricing_for_returns_table(stream, quantity, sales_pricing); + + // Generate returned date (based on ship date + lag) + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturnedDateSk); + let cr_returned_date_sk = generate_join_key( + &CrReturnedDateSk, + stream, + crate::config::Table::DateDim, + sales_row.get_cs_ship_date_sk(), + scaling, + )?; + + // Generate returned time + let stream = self + .abstract_generator + .get_random_number_stream(&CrReturnedTimeSk); + let cr_returned_time_sk = generate_join_key( + &CrReturnedTimeSk, + stream, + crate::config::Table::TimeDim, + 1, + scaling, + )?; + + // Generate ship mode + let stream = self + .abstract_generator + .get_random_number_stream(&CrShipModeSk); + let cr_ship_mode_sk = generate_join_key( + &CrShipModeSk, + stream, + crate::config::Table::ShipMode, + 1, + scaling, + )?; + + // Generate warehouse + let stream = self + .abstract_generator + .get_random_number_stream(&CrWarehouseSk); + let cr_warehouse_sk = generate_join_key( + &CrWarehouseSk, + stream, + crate::config::Table::Warehouse, + 1, + scaling, + )?; + + // Generate reason + let stream = self + .abstract_generator + .get_random_number_stream(&CrReasonSk); + let cr_reason_sk = generate_join_key( + &CrReasonSk, + stream, + crate::config::Table::Reason, + 1, + scaling, + )?; + + Ok(CatalogReturnsRow::new( + null_bit_map, + cr_returned_date_sk, + cr_returned_time_sk, + sales_row.get_cs_sold_item_sk(), // cr_item_sk from sales + sales_row.get_cs_bill_customer_sk(), // cr_refunded_customer_sk from sales bill + sales_row.get_cs_bill_cdemo_sk(), // cr_refunded_cdemo_sk from sales bill + sales_row.get_cs_bill_hdemo_sk(), // cr_refunded_hdemo_sk from sales bill + sales_row.get_cs_bill_addr_sk(), // cr_refunded_addr_sk from sales bill + cr_returning_customer_sk, + cr_returning_cdemo_sk, + cr_returning_hdemo_sk, + cr_returning_addr_sk, + sales_row.get_cs_call_center_sk(), // cr_call_center_sk from sales + sales_row.get_cs_catalog_page_sk(), // cr_catalog_page_sk from sales + cr_ship_mode_sk, + cr_warehouse_sk, + cr_reason_sk, + sales_row.get_cs_order_number(), // cr_order_number from sales + cr_pricing, + ) + .into()) + } +} + +impl Default for CatalogReturnsRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for CatalogReturnsRowGenerator { + fn generate_row_and_child_rows( + &mut self, + _row_number: i64, + _session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + // The catalog_returns table is a child of the catalog_sales table because you can only + // return things that have already been purchased. This method should only get called + // if we are generating the catalog_returns table in isolation. + // Otherwise catalog_returns is generated during the generation of the catalog_sales table + // via the generate_row method above. + // + // For now, we panic if called directly - the proper way is to generate through + // catalog_sales which calls our generate_row method. + panic!("CatalogReturnsRowGenerator::generate_row_and_child_rows should not be called directly. Use CatalogSalesRowGenerator to generate both sales and returns."); + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/catalog_sales_row.rs b/tpcdsgen/src/row/catalog_sales_row.rs new file mode 100644 index 00000000..1569e634 --- /dev/null +++ b/tpcdsgen/src/row/catalog_sales_row.rs @@ -0,0 +1,243 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog sales row structure + +use crate::generator::CatalogSalesGeneratorColumn; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row structure for catalog_sales table +#[derive(Debug, Clone)] +pub struct CatalogSalesRow { + null_bit_map: i64, + cs_sold_date_sk: i64, + cs_sold_time_sk: i64, + cs_ship_date_sk: i64, + cs_bill_customer_sk: i64, + cs_bill_cdemo_sk: i64, + cs_bill_hdemo_sk: i64, + cs_bill_addr_sk: i64, + cs_ship_customer_sk: i64, + cs_ship_cdemo_sk: i64, + cs_ship_hdemo_sk: i64, + cs_ship_addr_sk: i64, + cs_call_center_sk: i64, + cs_catalog_page_sk: i64, + cs_ship_mode_sk: i64, + cs_warehouse_sk: i64, + cs_sold_item_sk: i64, + cs_promo_sk: i64, + cs_order_number: i64, + cs_pricing: Pricing, +} + +impl CatalogSalesRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + cs_sold_date_sk: i64, + cs_sold_time_sk: i64, + cs_ship_date_sk: i64, + cs_bill_customer_sk: i64, + cs_bill_cdemo_sk: i64, + cs_bill_hdemo_sk: i64, + cs_bill_addr_sk: i64, + cs_ship_customer_sk: i64, + cs_ship_cdemo_sk: i64, + cs_ship_hdemo_sk: i64, + cs_ship_addr_sk: i64, + cs_call_center_sk: i64, + cs_catalog_page_sk: i64, + cs_ship_mode_sk: i64, + cs_warehouse_sk: i64, + cs_sold_item_sk: i64, + cs_promo_sk: i64, + cs_order_number: i64, + cs_pricing: Pricing, + ) -> Self { + CatalogSalesRow { + null_bit_map, + cs_sold_date_sk, + cs_sold_time_sk, + cs_ship_date_sk, + cs_bill_customer_sk, + cs_bill_cdemo_sk, + cs_bill_hdemo_sk, + cs_bill_addr_sk, + cs_ship_customer_sk, + cs_ship_cdemo_sk, + cs_ship_hdemo_sk, + cs_ship_addr_sk, + cs_call_center_sk, + cs_catalog_page_sk, + cs_ship_mode_sk, + cs_warehouse_sk, + cs_sold_item_sk, + cs_promo_sk, + cs_order_number, + cs_pricing, + } + } + + // Getters for fields needed by CatalogReturnsRowGenerator + pub fn get_cs_ship_date_sk(&self) -> i64 { + self.cs_ship_date_sk + } + + pub fn get_cs_sold_item_sk(&self) -> i64 { + self.cs_sold_item_sk + } + + pub fn get_cs_bill_customer_sk(&self) -> i64 { + self.cs_bill_customer_sk + } + + pub fn get_cs_bill_cdemo_sk(&self) -> i64 { + self.cs_bill_cdemo_sk + } + + pub fn get_cs_bill_hdemo_sk(&self) -> i64 { + self.cs_bill_hdemo_sk + } + + pub fn get_cs_bill_addr_sk(&self) -> i64 { + self.cs_bill_addr_sk + } + + pub fn get_cs_ship_customer_sk(&self) -> i64 { + self.cs_ship_customer_sk + } + + pub fn get_cs_ship_cdemo_sk(&self) -> i64 { + self.cs_ship_cdemo_sk + } + + pub fn get_cs_ship_addr_sk(&self) -> i64 { + self.cs_ship_addr_sk + } + + pub fn get_cs_call_center_sk(&self) -> i64 { + self.cs_call_center_sk + } + + pub fn get_cs_catalog_page_sk(&self) -> i64 { + self.cs_catalog_page_sk + } + + pub fn get_cs_order_number(&self) -> i64 { + self.cs_order_number + } + + pub fn get_cs_pricing(&self) -> &Pricing { + &self.cs_pricing + } + + fn is_null(&self, column: &CatalogSalesGeneratorColumn) -> bool { + let column_number = column.get_global_column_number(); + let first_column = CatalogSalesGeneratorColumn::CsSoldDateSk.get_global_column_number(); + let bit_position = column_number - first_column; + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key( + &self, + value: i64, + column: &CatalogSalesGeneratorColumn, + ) -> String { + if self.is_null(column) || value < 0 { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null( + &self, + value: T, + column: &CatalogSalesGeneratorColumn, + ) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for CatalogSalesRow { + fn get_values(&self) -> Vec { + use CatalogSalesGeneratorColumn::*; + + vec![ + self.get_string_or_null_for_key(self.cs_sold_date_sk, &CsSoldDateSk), + self.get_string_or_null_for_key(self.cs_sold_time_sk, &CsSoldTimeSk), + self.get_string_or_null_for_key(self.cs_ship_date_sk, &CsShipDateSk), + self.get_string_or_null_for_key(self.cs_bill_customer_sk, &CsBillCustomerSk), + self.get_string_or_null_for_key(self.cs_bill_cdemo_sk, &CsBillCdemoSk), + self.get_string_or_null_for_key(self.cs_bill_hdemo_sk, &CsBillHdemoSk), + self.get_string_or_null_for_key(self.cs_bill_addr_sk, &CsBillAddrSk), + self.get_string_or_null_for_key(self.cs_ship_customer_sk, &CsShipCustomerSk), + self.get_string_or_null_for_key(self.cs_ship_cdemo_sk, &CsShipCdemoSk), + self.get_string_or_null_for_key(self.cs_ship_hdemo_sk, &CsShipHdemoSk), + self.get_string_or_null_for_key(self.cs_ship_addr_sk, &CsShipAddrSk), + self.get_string_or_null_for_key(self.cs_call_center_sk, &CsCallCenterSk), + self.get_string_or_null_for_key(self.cs_catalog_page_sk, &CsCatalogPageSk), + self.get_string_or_null_for_key(self.cs_ship_mode_sk, &CsShipModeSk), + self.get_string_or_null(self.cs_warehouse_sk, &CsWarehouseSk), + self.get_string_or_null_for_key(self.cs_sold_item_sk, &CsSoldItemSk), + self.get_string_or_null_for_key(self.cs_promo_sk, &CsPromoSk), + self.get_string_or_null(self.cs_order_number, &CsOrderNumber), + self.get_string_or_null(self.cs_pricing.get_quantity(), &CsPricingQuantity), + self.get_string_or_null( + self.cs_pricing.get_wholesale_cost(), + &CsPricingWholesaleCost, + ), + self.get_string_or_null(self.cs_pricing.get_list_price(), &CsPricingListPrice), + self.get_string_or_null(self.cs_pricing.get_sales_price(), &CsPricingSalesPrice), + self.get_string_or_null( + self.cs_pricing.get_ext_discount_amount(), + &CsPricingExtDiscountAmount, + ), + self.get_string_or_null( + self.cs_pricing.get_ext_sales_price(), + &CsPricingExtSalesPrice, + ), + self.get_string_or_null( + self.cs_pricing.get_ext_wholesale_cost(), + &CsPricingExtWholesaleCost, + ), + self.get_string_or_null(self.cs_pricing.get_ext_list_price(), &CsPricingExtListPrice), + self.get_string_or_null(self.cs_pricing.get_ext_tax(), &CsPricingExtTax), + self.get_string_or_null(self.cs_pricing.get_coupon_amount(), &CsPricingCouponAmt), + self.get_string_or_null(self.cs_pricing.get_ext_ship_cost(), &CsPricingExtShipCost), + self.get_string_or_null(self.cs_pricing.get_net_paid(), &CsPricingNetPaid), + self.get_string_or_null( + self.cs_pricing.get_net_paid_including_tax(), + &CsPricingNetPaidIncTax, + ), + self.get_string_or_null( + self.cs_pricing.get_net_paid_including_shipping(), + &CsPricingNetPaidIncShip, + ), + self.get_string_or_null( + self.cs_pricing.get_net_paid_including_shipping_and_tax(), + &CsPricingNetPaidIncShipTax, + ), + self.get_string_or_null(self.cs_pricing.get_net_profit(), &CsPricingNetProfit), + ] + } +} + +use crate::generator::GeneratorColumn; diff --git a/tpcdsgen/src/row/catalog_sales_row_generator.rs b/tpcdsgen/src/row/catalog_sales_row_generator.rs new file mode 100644 index 00000000..9024df53 --- /dev/null +++ b/tpcdsgen/src/row/catalog_sales_row_generator.rs @@ -0,0 +1,479 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Catalog sales row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::CatalogSalesGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::permutations::{get_permutation_entry, make_permutation}; +use crate::random::RandomValueGenerator; +use crate::row::catalog_returns_row_generator::{CatalogReturnsRowGenerator, RETURN_PERCENT}; +use crate::row::catalog_sales_row::CatalogSalesRow; +use crate::row::{AbstractRowGenerator, GeneratedRow, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::match_surrogate_key; +use crate::table::Table; +use crate::types::{generate_pricing_for_sales_table, get_catalog_sales_pricing_limits, Date}; + +/// Minimum days from order to ship +pub const CS_MIN_SHIP_DELAY: i32 = 2; +/// Maximum days from order to ship +pub const CS_MAX_SHIP_DELAY: i32 = 90; +/// Percentage of orders that are gifts (ship to different customer) +const GIFT_PERCENTAGE: i32 = 10; + +/// Order information shared across line items in the same order +struct OrderInfo { + cs_sold_date_sk: i64, + cs_sold_time_sk: i64, + cs_call_center_sk: i64, + cs_bill_customer_sk: i64, + cs_bill_cdemo_sk: i64, + cs_bill_hdemo_sk: i64, + cs_bill_addr_sk: i64, + cs_ship_customer_sk: i64, + cs_ship_cdemo_sk: i64, + cs_ship_hdemo_sk: i64, + cs_ship_addr_sk: i64, + cs_order_number: i64, +} + +impl OrderInfo { + fn default() -> Self { + OrderInfo { + cs_sold_date_sk: 0, + cs_sold_time_sk: 0, + cs_call_center_sk: 0, + cs_bill_customer_sk: 0, + cs_bill_cdemo_sk: 0, + cs_bill_hdemo_sk: 0, + cs_bill_addr_sk: 0, + cs_ship_customer_sk: 0, + cs_ship_cdemo_sk: 0, + cs_ship_hdemo_sk: 0, + cs_ship_addr_sk: 0, + cs_order_number: 0, + } + } +} + +pub struct CatalogSalesRowGenerator { + abstract_generator: AbstractRowGenerator, + item_permutation: Option>, + julian_date: i64, + next_date_index: i64, + remaining_line_items: i32, + order_info: OrderInfo, + ticket_item_base: i32, + catalog_returns_generator: CatalogReturnsRowGenerator, +} + +impl CatalogSalesRowGenerator { + pub fn new() -> Self { + CatalogSalesRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::CatalogSales), + item_permutation: None, + julian_date: Date::JULIAN_DATA_START_DATE, + next_date_index: 0, + remaining_line_items: 0, + order_info: OrderInfo::default(), + ticket_item_base: 0, + catalog_returns_generator: CatalogReturnsRowGenerator::new(), + } + } + + fn generate_order_info(&mut self, row_number: i64, session: &Session) -> Result { + use CatalogSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Move to a new date if the row number is ahead of the nextDateIndex + while row_number > self.next_date_index { + self.julian_date += 1; + self.next_date_index += scaling + .get_row_count_for_date(crate::config::Table::CatalogSales, self.julian_date); + } + + let cs_sold_date_sk = self.julian_date; + + // cs_sold_time_sk uses cs_call_center_sk from previous order (like Java) + let stream = self + .abstract_generator + .get_random_number_stream(&CsSoldTimeSk); + let cs_sold_time_sk = generate_join_key( + &CsSoldTimeSk, + stream, + crate::config::Table::TimeDim, + self.order_info.cs_call_center_sk, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsCallCenterSk); + let cs_call_center_sk = if cs_sold_date_sk == -1 { + -1 + } else { + generate_join_key( + &CsCallCenterSk, + stream, + crate::config::Table::CallCenter, + cs_sold_date_sk, + scaling, + )? + }; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsBillCustomerSk); + let cs_bill_customer_sk = generate_join_key( + &CsBillCustomerSk, + stream, + crate::config::Table::Customer, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsBillCdemoSk); + let cs_bill_cdemo_sk = generate_join_key( + &CsBillCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsBillHdemoSk); + let cs_bill_hdemo_sk = generate_join_key( + &CsBillHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsBillAddrSk); + let cs_bill_addr_sk = generate_join_key( + &CsBillAddrSk, + stream, + crate::config::Table::CustomerAddress, + 1, + scaling, + )?; + + // Most orders are for the ordering customer, some are gifts (10%) + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipCustomerSk); + let gift_percentage = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + + let (cs_ship_customer_sk, cs_ship_cdemo_sk, cs_ship_hdemo_sk, cs_ship_addr_sk) = + if gift_percentage <= GIFT_PERCENTAGE { + // Gift order - ship to different customer + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipCustomerSk); + let ship_customer = generate_join_key( + &CsShipCustomerSk, + stream, + crate::config::Table::Customer, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipCdemoSk); + let ship_cdemo = generate_join_key( + &CsShipCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipHdemoSk); + let ship_hdemo = generate_join_key( + &CsShipHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipAddrSk); + let ship_addr = generate_join_key( + &CsShipAddrSk, + stream, + crate::config::Table::CustomerAddress, + 2, + scaling, + )?; + + (ship_customer, ship_cdemo, ship_hdemo, ship_addr) + } else { + // Same as bill customer + ( + cs_bill_customer_sk, + cs_bill_cdemo_sk, + cs_bill_hdemo_sk, + cs_bill_addr_sk, + ) + }; + + let cs_order_number = row_number; + + Ok(OrderInfo { + cs_sold_date_sk, + cs_sold_time_sk, + cs_call_center_sk, + cs_bill_customer_sk, + cs_bill_cdemo_sk, + cs_bill_hdemo_sk, + cs_bill_addr_sk, + cs_ship_customer_sk, + cs_ship_cdemo_sk, + cs_ship_hdemo_sk, + cs_ship_addr_sk, + cs_order_number, + }) + } + + fn is_last_row_in_order(&self) -> bool { + self.remaining_line_items == 0 + } + + /// Consume remaining seeds for the child (catalog_returns) generator. + pub fn consume_child_seeds(&mut self) { + self.catalog_returns_generator + .consume_remaining_seeds_for_row(); + } +} + +impl Default for CatalogSalesRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for CatalogSalesRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + use CatalogSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + let item_count = scaling.get_id_count(crate::config::Table::Item) as usize; + + // Initialize item permutation and date tracking if needed + if self.item_permutation.is_none() { + let stream = self.abstract_generator.get_random_number_stream(&CsPermute); + self.item_permutation = Some(make_permutation(item_count, stream)); + + // Initialize date tracking + self.julian_date = Date::JULIAN_DATA_START_DATE; + self.next_date_index = scaling + .get_row_count_for_date(crate::config::Table::CatalogSales, self.julian_date) + + 1; + } + + // Start a new order if we've finished the previous one + if self.remaining_line_items == 0 { + self.order_info = self.generate_order_info(row_number, session)?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CsSoldItemSk); + self.ticket_item_base = + RandomValueGenerator::generate_uniform_random_int(1, item_count as i32, stream); + + let stream = self + .abstract_generator + .get_random_number_stream(&CsOrderNumber); + self.remaining_line_items = + RandomValueGenerator::generate_uniform_random_int(4, 14, stream); + } + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&CsNulls); + let null_bit_map = create_null_bit_map(Table::CatalogSales, stream); + + // Orders are shipped some number of days after they are ordered + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipDateSk); + let shipping_lag = RandomValueGenerator::generate_uniform_random_int( + CS_MIN_SHIP_DELAY, + CS_MAX_SHIP_DELAY, + stream, + ); + let cs_ship_date_sk = if self.order_info.cs_sold_date_sk == -1 { + -1 + } else { + self.order_info.cs_sold_date_sk + shipping_lag as i64 + }; + + // Items need to be unique within an order + // Use a sequence within the permutation + self.ticket_item_base += 1; + if self.ticket_item_base > item_count as i32 { + self.ticket_item_base = 1; + } + + // Get item from permutation and match surrogate key for SCD + let permutation = self.item_permutation.as_ref().unwrap(); + let item_key = get_permutation_entry(permutation, self.ticket_item_base); + let cs_sold_item_sk = match_surrogate_key( + item_key as i64, + self.order_info.cs_sold_date_sk, + crate::config::Table::Item, + scaling, + ); + + // Catalog page needs to be from a catalog active at the time of the sale + let stream = self + .abstract_generator + .get_random_number_stream(&CsCatalogPageSk); + let cs_catalog_page_sk = if self.order_info.cs_sold_date_sk == -1 { + -1 + } else { + generate_join_key( + &CsCatalogPageSk, + stream, + crate::config::Table::CatalogPage, + self.order_info.cs_sold_date_sk, + scaling, + )? + }; + + // Generate ship mode + let stream = self + .abstract_generator + .get_random_number_stream(&CsShipModeSk); + let cs_ship_mode_sk = generate_join_key( + &CsShipModeSk, + stream, + crate::config::Table::ShipMode, + 1, + scaling, + )?; + + // Generate warehouse + let stream = self + .abstract_generator + .get_random_number_stream(&CsWarehouseSk); + let cs_warehouse_sk = generate_join_key( + &CsWarehouseSk, + stream, + crate::config::Table::Warehouse, + 1, + scaling, + )?; + + // Generate promo sk + let stream = self.abstract_generator.get_random_number_stream(&CsPromoSk); + let cs_promo_sk = generate_join_key( + &CsPromoSk, + stream, + crate::config::Table::Promotion, + 1, + scaling, + )?; + + // Generate pricing + let stream = self.abstract_generator.get_random_number_stream(&CsPricing); + let cs_pricing = + generate_pricing_for_sales_table(&get_catalog_sales_pricing_limits(), stream); + + let catalog_sales_row = CatalogSalesRow::new( + null_bit_map, + self.order_info.cs_sold_date_sk, + self.order_info.cs_sold_time_sk, + cs_ship_date_sk, + self.order_info.cs_bill_customer_sk, + self.order_info.cs_bill_cdemo_sk, + self.order_info.cs_bill_hdemo_sk, + self.order_info.cs_bill_addr_sk, + self.order_info.cs_ship_customer_sk, + self.order_info.cs_ship_cdemo_sk, + self.order_info.cs_ship_hdemo_sk, + self.order_info.cs_ship_addr_sk, + self.order_info.cs_call_center_sk, + cs_catalog_page_sk, + cs_ship_mode_sk, + cs_warehouse_sk, + cs_sold_item_sk, + cs_promo_sk, + self.order_info.cs_order_number, + cs_pricing, + ); + + // Check if this sale gets returned (10% return rate) + // We check and generate the return BEFORE moving the sales row to avoid cloning + let stream = self + .abstract_generator + .get_random_number_stream(&CrIsReturned); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + + // Generate return row if applicable (using reference before we move sales_row) + let return_row = if random_int < RETURN_PERCENT { + Some( + self.catalog_returns_generator + .generate_row(session, &catalog_sales_row)?, + ) + } else { + None + }; + + // Now move (not clone) the sales row into the result + let mut generated_rows: Vec = Vec::with_capacity(2); + generated_rows.push(catalog_sales_row.into()); + + if let Some(ret_row) = return_row { + generated_rows.push(ret_row); + } + + self.remaining_line_items -= 1; + + Ok(RowGeneratorResult::new_with_multiple( + generated_rows, + self.is_last_row_in_order(), + )) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/customer_address_row.rs b/tpcdsgen/src/row/customer_address_row.rs new file mode 100644 index 00000000..af56f130 --- /dev/null +++ b/tpcdsgen/src/row/customer_address_row.rs @@ -0,0 +1,106 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer address row (CustomerAddressRow) + +use crate::row::TableRow; +use crate::types::Address; + +/// Customer address table row (CustomerAddressRow) +#[derive(Debug, Clone)] +pub struct CustomerAddressRow { + null_bit_map: i64, + ca_addr_sk: i64, + ca_addr_id: String, + ca_address: Address, + ca_location_type: String, +} + +impl CustomerAddressRow { + pub fn new( + null_bit_map: i64, + ca_addr_sk: i64, + ca_addr_id: String, + ca_address: Address, + ca_location_type: String, + ) -> Self { + CustomerAddressRow { + null_bit_map, + ca_addr_sk, + ca_addr_id, + ca_address, + ca_location_type, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + /// Convert key to string or empty string if null (getStringOrNullForKey) + fn get_string_or_null_for_key(&self, value: i64, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_ca_addr_sk(&self) -> i64 { + self.ca_addr_sk + } + + pub fn get_ca_addr_id(&self) -> &str { + &self.ca_addr_id + } + + pub fn get_ca_address(&self) -> &Address { + &self.ca_address + } + + pub fn get_ca_location_type(&self) -> &str { + &self.ca_location_type + } +} + +impl TableRow for CustomerAddressRow { + fn get_values(&self) -> Vec { + // Column positions match Java CustomerAddressColumn ordinals (0-12) + vec![ + self.get_string_or_null_for_key(self.ca_addr_sk, 0), // CA_ADDRESS_SK + self.get_string_or_null(&self.ca_addr_id, 1), // CA_ADDRESS_ID + self.get_string_or_null(self.ca_address.get_street_number(), 2), // CA_STREET_NUMBER + self.get_string_or_null(self.ca_address.get_street_name(), 3), // CA_STREET_NAME + self.get_string_or_null(self.ca_address.get_street_type(), 4), // CA_STREET_TYPE + self.get_string_or_null(self.ca_address.get_suite_number(), 5), // CA_SUITE_NUMBER + self.get_string_or_null(self.ca_address.get_city(), 6), // CA_CITY + self.get_string_or_null(self.ca_address.get_county().unwrap_or(""), 7), // CA_COUNTY + self.get_string_or_null(self.ca_address.get_state(), 8), // CA_STATE + self.get_string_or_null(format!("{:05}", self.ca_address.get_zip()), 9), // CA_ZIP + self.get_string_or_null(self.ca_address.get_country(), 10), // CA_COUNTRY + self.get_string_or_null(self.ca_address.get_gmt_offset(), 11), // CA_GMT_OFFSET + self.get_string_or_null(&self.ca_location_type, 12), // CA_LOCATION_TYPE + ] + } +} diff --git a/tpcdsgen/src/row/customer_address_row_generator.rs b/tpcdsgen/src/row/customer_address_row_generator.rs new file mode 100644 index 00000000..a6f47377 --- /dev/null +++ b/tpcdsgen/src/row/customer_address_row_generator.rs @@ -0,0 +1,110 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer address row generator (CustomerAddressRowGenerator) + +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::location_types_distribution::{ + LocationTypeWeights, LocationTypesDistribution, +}; +use crate::error::Result; +use crate::generator::CustomerAddressGeneratorColumn; +use crate::nulls::create_null_bit_map; +use crate::row::{AbstractRowGenerator, CustomerAddressRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::Address; + +/// Row generator for the CUSTOMER_ADDRESS table (CustomerAddressRowGenerator) +pub struct CustomerAddressRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for CustomerAddressRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl CustomerAddressRowGenerator { + /// Create a new CustomerAddressRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::CustomerAddress), + } + } + + /// Generate a CustomerAddressRow with realistic data following Java implementation + fn generate_customer_address_row( + &mut self, + row_number: i64, + session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&CustomerAddressGeneratorColumn::CaNulls); + let null_bit_map = create_null_bit_map(Table::CustomerAddress, nulls_stream); + + let ca_addr_sk = row_number; + let ca_addr_id = make_business_key(row_number); + + // Generate address + let scaling = session.get_scaling(); + let address_stream = self + .abstract_generator + .get_random_number_stream(&CustomerAddressGeneratorColumn::CaAddress); + let ca_address = + Address::make_address_for_column(Table::CustomerAddress, address_stream, scaling)?; + + // Generate location type using UNIFORM weights (matches Java) + let location_type_stream = self + .abstract_generator + .get_random_number_stream(&CustomerAddressGeneratorColumn::CaLocationType); + let ca_location_type = LocationTypesDistribution::pick_random_location_type( + LocationTypeWeights::Uniform, + location_type_stream, + )?; + + Ok(CustomerAddressRow::new( + null_bit_map, + ca_addr_sk, + ca_addr_id, + ca_address, + ca_location_type, + )) + } +} + +impl RowGenerator for CustomerAddressRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_customer_address_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/customer_demographics_row.rs b/tpcdsgen/src/row/customer_demographics_row.rs new file mode 100644 index 00000000..53b3e652 --- /dev/null +++ b/tpcdsgen/src/row/customer_demographics_row.rs @@ -0,0 +1,76 @@ +use crate::row::TableRow; + +/// Customer demographics table row (CustomerDemographicsRow) +#[derive(Debug, Clone)] +pub struct CustomerDemographicsRow { + null_bit_map: i64, + cd_demo_sk: i64, + cd_gender: String, + cd_marital_status: String, + cd_education_status: String, + cd_purchase_estimate: i32, + cd_credit_rating: String, + cd_dep_count: i32, + cd_dep_employed_count: i32, + cd_dep_college_count: i32, +} + +impl CustomerDemographicsRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + cd_demo_sk: i64, + cd_gender: String, + cd_marital_status: String, + cd_education_status: String, + cd_purchase_estimate: i32, + cd_credit_rating: String, + cd_dep_count: i32, + cd_dep_employed_count: i32, + cd_dep_college_count: i32, + ) -> Self { + CustomerDemographicsRow { + null_bit_map, + cd_demo_sk, + cd_gender, + cd_marital_status, + cd_education_status, + cd_purchase_estimate, + cd_credit_rating, + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for CustomerDemographicsRow { + fn get_values(&self) -> Vec { + // Column positions match Java CustomerDemographicsGeneratorColumn (0-8) + vec![ + self.get_string_or_null(self.cd_demo_sk, 0), + self.get_string_or_null(&self.cd_gender, 1), + self.get_string_or_null(&self.cd_marital_status, 2), + self.get_string_or_null(&self.cd_education_status, 3), + self.get_string_or_null(self.cd_purchase_estimate, 4), + self.get_string_or_null(&self.cd_credit_rating, 5), + self.get_string_or_null(self.cd_dep_count, 6), + self.get_string_or_null(self.cd_dep_employed_count, 7), + self.get_string_or_null(self.cd_dep_college_count, 8), + ] + } +} diff --git a/tpcdsgen/src/row/customer_demographics_row_generator.rs b/tpcdsgen/src/row/customer_demographics_row_generator.rs new file mode 100644 index 00000000..0a25ce3f --- /dev/null +++ b/tpcdsgen/src/row/customer_demographics_row_generator.rs @@ -0,0 +1,126 @@ +use crate::config::Session; +use crate::distribution::DemographicsDistributions; +use crate::error::Result; +use crate::generator::CustomerDemographicsGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, CustomerDemographicsRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; + +/// Row generator for the CUSTOMER_DEMOGRAPHICS table (CustomerDemographicsRowGenerator) +pub struct CustomerDemographicsRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for CustomerDemographicsRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl CustomerDemographicsRowGenerator { + // Constants matching Java implementation + const MAX_CHILDREN: i64 = 7; + const MAX_EMPLOYED: i64 = 7; + const MAX_COLLEGE: i64 = 7; + + /// Create a new CustomerDemographicsRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::CustomerDemographics), + } + } + + /// Generate a CustomerDemographicsRow with realistic data following Java implementation + fn generate_customer_demographics_row( + &mut self, + row_number: i64, + _session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&CustomerDemographicsGeneratorColumn::CdNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = + RandomValueGenerator::generate_uniform_random_key(1, i32::MAX as i64, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::CustomerDemographics.get_null_basis_points() { + bit_map & !Table::CustomerDemographics.get_not_null_bit_map() + } else { + 0 + }; + + // Generate demographics using index-based cartesian product (algorithm) + let cd_demo_sk = row_number; + let mut index = cd_demo_sk - 1; + + // Get gender and divide index + let cd_gender = DemographicsDistributions::get_gender_for_index_mod_size(index); + index /= DemographicsDistributions::get_gender_size() as i64; + + // Get marital status and divide index + let cd_marital_status = + DemographicsDistributions::get_marital_status_for_index_mod_size(index); + index /= DemographicsDistributions::get_marital_status_size() as i64; + + // Get education and divide index + let cd_education_status = + DemographicsDistributions::get_education_for_index_mod_size(index); + index /= DemographicsDistributions::get_education_size() as i64; + + // Get purchase band and divide index + let cd_purchase_estimate = + DemographicsDistributions::get_purchase_band_for_index_mod_size(index); + index /= DemographicsDistributions::get_purchase_band_size() as i64; + + // Get credit rating and divide index + let cd_credit_rating = + DemographicsDistributions::get_credit_rating_for_index_mod_size(index); + index /= DemographicsDistributions::get_credit_rating_size() as i64; + + // Get dependent counts using modulo (no division lookup needed) + let cd_dep_count = (index % Self::MAX_CHILDREN) as i32; + index /= Self::MAX_CHILDREN; + + let cd_dep_employed_count = (index % Self::MAX_EMPLOYED) as i32; + index /= Self::MAX_EMPLOYED; + + let cd_dep_college_count = (index % Self::MAX_COLLEGE) as i32; + + Ok(CustomerDemographicsRow::new( + null_bit_map, + cd_demo_sk, + cd_gender.to_string(), + cd_marital_status.to_string(), + cd_education_status.to_string(), + cd_purchase_estimate, + cd_credit_rating.to_string(), + cd_dep_count, + cd_dep_employed_count, + cd_dep_college_count, + )) + } +} + +impl RowGenerator for CustomerDemographicsRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_customer_demographics_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/customer_row.rs b/tpcdsgen/src/row/customer_row.rs new file mode 100644 index 00000000..6ad2b523 --- /dev/null +++ b/tpcdsgen/src/row/customer_row.rs @@ -0,0 +1,166 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer row definition (CustomerRow) + +use crate::generator::CustomerGeneratorColumn; +use crate::row::TableRow; + +/// Customer row (CustomerRow) +#[derive(Debug, Clone)] +pub struct CustomerRow { + null_bit_map: i64, + c_customer_sk: i64, + c_customer_id: String, + c_current_cdemo_sk: i64, + c_current_hdemo_sk: i64, + c_current_addr_sk: i64, + c_first_shipto_date_id: i32, + c_first_sales_date_id: i32, + c_salutation: String, + c_first_name: String, + c_last_name: String, + c_preferred_cust_flag: bool, + c_birth_day: i32, + c_birth_month: i32, + c_birth_year: i32, + c_birth_country: String, + c_login: Option, // always null in the Java implementation + c_email_address: String, + c_last_review_date: i32, +} + +impl CustomerRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + c_customer_sk: i64, + c_customer_id: String, + c_current_cdemo_sk: i64, + c_current_hdemo_sk: i64, + c_current_addr_sk: i64, + c_first_shipto_date_id: i32, + c_first_sales_date_id: i32, + c_salutation: String, + c_first_name: String, + c_last_name: String, + c_preferred_cust_flag: bool, + c_birth_day: i32, + c_birth_month: i32, + c_birth_year: i32, + c_birth_country: String, + c_email_address: String, + c_last_review_date: i32, + null_bit_map: i64, + ) -> Self { + CustomerRow { + null_bit_map, + c_customer_sk, + c_customer_id, + c_current_cdemo_sk, + c_current_hdemo_sk, + c_current_addr_sk, + c_first_shipto_date_id, + c_first_sales_date_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_login: None, // never gets set to anything + c_email_address, + c_last_review_date, + } + } + + /// Check if a column is null based on the null bit map + fn is_null(&self, column: CustomerGeneratorColumn) -> bool { + let position = column.get_global_column_number() + - CustomerGeneratorColumn::CCustomerSk.get_global_column_number(); + (self.null_bit_map & (1 << position)) != 0 + } + + /// Get string or null for key fields (surrogate keys) + fn get_string_or_null_for_key(&self, value: i64, column: CustomerGeneratorColumn) -> String { + if self.is_null(column) || value < 0 { + String::new() + } else { + value.to_string() + } + } + + /// Get string or null for string fields + fn get_string_or_null(&self, value: &str, column: CustomerGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + /// Get string or null for integer fields + fn get_string_or_null_for_int(&self, value: i32, column: CustomerGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + /// Get string or null for boolean fields (Y/N format) + fn get_string_or_null_for_boolean( + &self, + value: bool, + column: CustomerGeneratorColumn, + ) -> String { + if self.is_null(column) { + String::new() + } else if value { + "Y".to_string() + } else { + "N".to_string() + } + } +} + +impl TableRow for CustomerRow { + fn get_values(&self) -> Vec { + use CustomerGeneratorColumn::*; + + vec![ + self.get_string_or_null_for_key(self.c_customer_sk, CCustomerSk), + self.get_string_or_null(&self.c_customer_id, CCustomerId), + self.get_string_or_null_for_key(self.c_current_cdemo_sk, CCurrentCdemoSk), + self.get_string_or_null_for_key(self.c_current_hdemo_sk, CCurrentHdemoSk), + self.get_string_or_null_for_key(self.c_current_addr_sk, CCurrentAddrSk), + self.get_string_or_null_for_int(self.c_first_shipto_date_id, CFirstShiptoDateId), + self.get_string_or_null_for_int(self.c_first_sales_date_id, CFirstSalesDateId), + self.get_string_or_null(&self.c_salutation, CSalutation), + self.get_string_or_null(&self.c_first_name, CFirstName), + self.get_string_or_null(&self.c_last_name, CLastName), + self.get_string_or_null_for_boolean(self.c_preferred_cust_flag, CPreferredCustFlag), + self.get_string_or_null_for_int(self.c_birth_day, CBirthDay), + self.get_string_or_null_for_int(self.c_birth_month, CBirthMonth), + self.get_string_or_null_for_int(self.c_birth_year, CBirthYear), + self.get_string_or_null(&self.c_birth_country, CBirthCountry), + self.c_login.clone().unwrap_or_default(), // always null/empty + self.get_string_or_null(&self.c_email_address, CEmailAddress), + self.get_string_or_null_for_int(self.c_last_review_date, CLastReviewDate), + ] + } +} + +use crate::generator::GeneratorColumn; diff --git a/tpcdsgen/src/row/customer_row_generator.rs b/tpcdsgen/src/row/customer_row_generator.rs new file mode 100644 index 00000000..e75e9b3a --- /dev/null +++ b/tpcdsgen/src/row/customer_row_generator.rs @@ -0,0 +1,218 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Customer row generator (CustomerRowGenerator) + +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::{ + pick_random_country, FirstNamesWeights, NamesDistributions, SalutationsWeights, +}; +use crate::error::Result; +use crate::generator::CustomerGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::customer_row::CustomerRow; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::Date; + +/// Customer row generator (CustomerRowGenerator) +pub struct CustomerRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl CustomerRowGenerator { + pub fn new() -> Self { + CustomerRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::Customer), + } + } + + fn generate_customer_row(&mut self, row_number: i64, session: &Session) -> Result { + use CustomerGeneratorColumn::*; + + let c_customer_sk = row_number; + let c_customer_id = make_business_key(row_number); + + // Preferred customer flag - MUST BE FIRST (matches Java order line 72) + let stream = self + .abstract_generator + .get_random_number_stream(&CPreferredCustFlag); + let random_int = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + let c_preferred_percent = 50; + let c_preferred_cust_flag = random_int < c_preferred_percent; + + let scaling = session.get_scaling(); + + // Generate join keys (matches Java order lines 77-79) + let stream = self + .abstract_generator + .get_random_number_stream(&CCurrentHdemoSk); + let c_current_hdemo_sk = generate_join_key( + &CCurrentHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CCurrentCdemoSk); + let c_current_cdemo_sk = generate_join_key( + &CCurrentCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&CCurrentAddrSk); + let c_current_addr_sk = generate_join_key( + &CCurrentAddrSk, + stream, + crate::config::Table::CustomerAddress, + c_customer_sk, + scaling, + )?; + + // Name generation (matches Java order lines 81-85) + let stream = self + .abstract_generator + .get_random_number_stream(&CFirstName); + let name_index = + NamesDistributions::pick_random_index(FirstNamesWeights::GeneralFrequency, stream)?; + let c_first_name = NamesDistributions::get_first_name_from_index(name_index)?.to_string(); + + let stream = self.abstract_generator.get_random_number_stream(&CLastName); + let c_last_name = NamesDistributions::pick_random_last_name(stream)?.to_string(); + + // Salutation based on gender frequency + let female_name_weight = NamesDistributions::get_weight_for_index( + name_index, + FirstNamesWeights::FemaleFrequency, + )?; + let salutation_weight = if female_name_weight == 0 { + SalutationsWeights::Male + } else { + SalutationsWeights::Female + }; + let stream = self + .abstract_generator + .get_random_number_stream(&CSalutation); + let c_salutation = + NamesDistributions::pick_random_salutation(salutation_weight, stream)?.to_string(); + + // Birthday generation (matches Java order lines 87-95) + let max_birthday = Date::new(1992, 12, 31); + let min_birthday = Date::new(1924, 1, 1); + let one_year_ago = Date::from_julian_days(Date::JULIAN_TODAYS_DATE - 365); + let ten_years_ago = Date::from_julian_days(Date::JULIAN_TODAYS_DATE - 3650); + let today = Date::from_julian_days(Date::JULIAN_TODAYS_DATE); + + let stream = self.abstract_generator.get_random_number_stream(&CBirthDay); + let birthday = + RandomValueGenerator::generate_uniform_random_date(min_birthday, max_birthday, stream)?; + let c_birth_day = birthday.day(); + let c_birth_month = birthday.month(); + let c_birth_year = birthday.year(); + + // Email generation (matches Java order line 97) + let stream = self + .abstract_generator + .get_random_number_stream(&CEmailAddress); + let c_email_address = + RandomValueGenerator::generate_random_email(&c_first_name, &c_last_name, stream); + + // Last review date (matches Java order lines 98-99) + let stream = self + .abstract_generator + .get_random_number_stream(&CLastReviewDate); + let last_review_date = + RandomValueGenerator::generate_uniform_random_date(one_year_ago, today, stream)?; + let c_last_review_date = last_review_date.to_julian_days(); + + // First sales date (matches Java order lines 100-102) + let stream = self + .abstract_generator + .get_random_number_stream(&CFirstSalesDateId); + let first_sales_date = + RandomValueGenerator::generate_uniform_random_date(ten_years_ago, today, stream)?; + let c_first_sales_date_id = first_sales_date.to_julian_days(); + let c_first_shipto_date_id = c_first_sales_date_id + 30; + + // Birth country (matches Java order line 104) + let stream = self + .abstract_generator + .get_random_number_stream(&CBirthCountry); + let c_birth_country = pick_random_country(stream)?.to_string(); + + // Generate null bit map (matches Java order line 123) + let stream = self.abstract_generator.get_random_number_stream(&CNulls); + let null_bit_map = create_null_bit_map(Table::Customer, stream); + + Ok(CustomerRow::new( + c_customer_sk, + c_customer_id, + c_current_cdemo_sk, + c_current_hdemo_sk, + c_current_addr_sk, + c_first_shipto_date_id, + c_first_sales_date_id, + c_salutation, + c_first_name, + c_last_name, + c_preferred_cust_flag, + c_birth_day, + c_birth_month, + c_birth_year, + c_birth_country, + c_email_address, + c_last_review_date, + null_bit_map, + )) + } +} + +impl Default for CustomerRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for CustomerRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_customer_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/date_dim_row.rs b/tpcdsgen/src/row/date_dim_row.rs new file mode 100644 index 00000000..b6ba83cf --- /dev/null +++ b/tpcdsgen/src/row/date_dim_row.rs @@ -0,0 +1,185 @@ +use crate::row::TableRow; +use crate::types::Date; + +/// Represents a row in the DATE_DIM table +#[derive(Debug, Clone)] +pub struct DateDimRow { + // Null bitmap for handling NULL values + null_bit_map: i64, + + // Primary key + pub d_date_sk: i64, + + // Date identifier (YYYYMMDD format) + pub d_date_id: String, + + // Date value + pub d_date: Date, + + // Sequence numbers + pub d_month_seq: i32, + pub d_week_seq: i32, + pub d_quarter_seq: i32, + + // Year components + pub d_year: i32, + pub d_dow: i32, // Day of week (0-6) + pub d_moy: i32, // Month of year (1-12) + pub d_dom: i32, // Day of month (1-31) + pub d_qoy: i32, // Quarter of year (1-4) + + // Fiscal year components + pub d_fy_year: i32, + pub d_fy_quarter_seq: i32, + pub d_fy_week_seq: i32, + + // Names + pub d_day_name: String, // Monday, Tuesday, etc. + pub d_quarter_name: String, // 2024Q1, 2024Q2, etc. + + // Flags + pub d_holiday: bool, + pub d_weekend: bool, + pub d_following_holiday: bool, + + // First and last day of month + pub d_first_dom: i32, + pub d_last_dom: i32, + + // Same day references (julian days) + pub d_same_day_ly: i32, // Same day last year + pub d_same_day_lq: i32, // Same day last quarter + + // Current flags (relative to a reference date) + pub d_current_day: bool, + pub d_current_week: bool, + pub d_current_month: bool, + pub d_current_quarter: bool, + pub d_current_year: bool, +} + +impl DateDimRow { + /// Create a new DateDimRow with all fields + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + d_date_sk: i64, + d_date_id: String, + d_date: Date, + d_month_seq: i32, + d_week_seq: i32, + d_quarter_seq: i32, + d_year: i32, + d_dow: i32, + d_moy: i32, + d_dom: i32, + d_qoy: i32, + d_fy_year: i32, + d_fy_quarter_seq: i32, + d_fy_week_seq: i32, + d_day_name: String, + d_quarter_name: String, + d_holiday: bool, + d_weekend: bool, + d_following_holiday: bool, + d_first_dom: i32, + d_last_dom: i32, + d_same_day_ly: i32, + d_same_day_lq: i32, + d_current_day: bool, + d_current_week: bool, + d_current_month: bool, + d_current_quarter: bool, + d_current_year: bool, + ) -> Self { + DateDimRow { + null_bit_map, + d_date_sk, + d_date_id, + d_date, + d_month_seq, + d_week_seq, + d_quarter_seq, + d_year, + d_dow, + d_moy, + d_dom, + d_qoy, + d_fy_year, + d_fy_quarter_seq, + d_fy_week_seq, + d_day_name, + d_quarter_name, + d_holiday, + d_weekend, + d_following_holiday, + d_first_dom, + d_last_dom, + d_same_day_ly, + d_same_day_lq, + d_current_day, + d_current_week, + d_current_month, + d_current_quarter, + d_current_year, + } + } + + /// Check if a column should be NULL based on the null bitmap + fn is_field_null(&self, column_index: usize) -> bool { + (self.null_bit_map & (1 << column_index)) != 0 + } + + /// Format a boolean value for output + fn format_boolean(value: bool) -> &'static str { + if value { + "Y" + } else { + "N" + } + } + + /// Get string value or NULL for optional fields + fn get_string_or_null(&self, value: T, column_index: usize) -> String { + if self.is_field_null(column_index) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for DateDimRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null(self.d_date_sk, 0), + self.get_string_or_null(&self.d_date_id, 1), + self.get_string_or_null(self.d_date.to_string(), 2), + self.get_string_or_null(self.d_month_seq, 3), + self.get_string_or_null(self.d_week_seq, 4), + self.get_string_or_null(self.d_quarter_seq, 5), + self.get_string_or_null(self.d_year, 6), + self.get_string_or_null(self.d_dow, 7), + self.get_string_or_null(self.d_moy, 8), + self.get_string_or_null(self.d_dom, 9), + self.get_string_or_null(self.d_qoy, 10), + self.get_string_or_null(self.d_fy_year, 11), + self.get_string_or_null(self.d_fy_quarter_seq, 12), + self.get_string_or_null(self.d_fy_week_seq, 13), + self.get_string_or_null(&self.d_day_name, 14), + self.get_string_or_null(&self.d_quarter_name, 15), + self.get_string_or_null(Self::format_boolean(self.d_holiday), 16), + self.get_string_or_null(Self::format_boolean(self.d_weekend), 17), + self.get_string_or_null(Self::format_boolean(self.d_following_holiday), 18), + self.get_string_or_null(self.d_first_dom, 19), + self.get_string_or_null(self.d_last_dom, 20), + self.get_string_or_null(self.d_same_day_ly, 21), + self.get_string_or_null(self.d_same_day_lq, 22), + self.get_string_or_null(Self::format_boolean(self.d_current_day), 23), + self.get_string_or_null(Self::format_boolean(self.d_current_week), 24), + self.get_string_or_null(Self::format_boolean(self.d_current_month), 25), + self.get_string_or_null(Self::format_boolean(self.d_current_quarter), 26), + self.get_string_or_null(Self::format_boolean(self.d_current_year), 27), + ] + } +} diff --git a/tpcdsgen/src/row/date_dim_row_generator.rs b/tpcdsgen/src/row/date_dim_row_generator.rs new file mode 100644 index 00000000..86f0200f --- /dev/null +++ b/tpcdsgen/src/row/date_dim_row_generator.rs @@ -0,0 +1,171 @@ +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::CalendarDistribution; +use crate::row::{AbstractRowGenerator, DateDimRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::Date; + +/// Constants for date calculations +const TODAYS_DATE: Date = Date::new(2003, 1, 8); // January 8, 2003 +const CURRENT_QUARTER: i32 = 1; +const CURRENT_WEEK: i32 = 2; // Week number for TODAYS_DATE + +const WEEKDAY_NAMES: [&str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; + +pub struct DateDimRowGenerator { + base: AbstractRowGenerator, +} + +impl Default for DateDimRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl DateDimRowGenerator { + pub fn new() -> Self { + DateDimRowGenerator { + base: AbstractRowGenerator::new(Table::DateDim), + } + } +} + +impl RowGenerator for DateDimRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + _session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> crate::error::Result { + // Create null bitmap - DateDim has very few nulls + let null_bit_map = 0i64; + + // Base date is January 1, 1900 + let base_date = Date::new(1900, 1, 1); + let base_julian = base_date.to_julian_days(); + + // Each row represents one day, starting from base date + let d_date_sk = row_number + base_julian as i64; + let d_date_id = make_business_key(d_date_sk); + let date = Date::from_julian_days(d_date_sk as i32); + + // Extract date components + let d_year = date.year(); + let d_dow = date.day_of_week(); // 0 = Sunday, 6 = Saturday + let d_moy = date.month(); + let d_dom = date.day(); + + // Calculate sequence numbers (assumes table starts on year boundary) + let d_week_seq = ((row_number + 6) / 7) as i32; + let d_month_seq = (d_year - 1900) * 12 + d_moy - 1; + // Note: Java has a bug where it uses dMoy/3 instead of (dMoy-1)/3 + // This incorrectly puts March in Q2. We replicate this bug for compatibility. + let d_quarter_seq = (d_year - 1900) * 4 + d_moy / 3 + 1; + + // Get day index for distributions (1-based day of year) + let day_index = date.day_of_year(); + let d_qoy = CalendarDistribution::get_quarter_at_index(day_index); + + // Fiscal year is identical to calendar year in TPC-DS + let d_fy_year = d_year; + let d_fy_quarter_seq = d_quarter_seq; + let d_fy_week_seq = d_week_seq; + + // Get day name + let d_day_name = WEEKDAY_NAMES[d_dow as usize].to_string(); + + // Calculate quarter name (e.g., "2024Q1") + let d_quarter_name = format!("{}Q{}", d_year, d_qoy); + + // Determine holiday and weekend flags + let d_holiday = CalendarDistribution::get_is_holiday_flag_at_index(day_index) != 0; + // Note: Java implementation has a bug where Friday and Saturday are weekend days + // We replicate this bug for compatibility + let d_weekend = d_dow == 5 || d_dow == 6; // Friday or Saturday (bug compatibility) + + // Following holiday flag + let d_following_holiday = if day_index == 1 { + // First day of year - check last day of previous year + // Note: This matches the C/Java bug where it uses 365 + leap year flag + let last_day_prev_year = if Date::is_leap_year(d_year - 1) { + 366 + } else { + 365 + }; + CalendarDistribution::get_is_holiday_flag_at_index(last_day_prev_year) != 0 + } else { + CalendarDistribution::get_is_holiday_flag_at_index(day_index - 1) != 0 + }; + + // First and last day of month (as julian days) + let first_of_month = Date::new(d_year, d_moy, 1); + let d_first_dom = first_of_month.to_julian_days(); + let d_last_dom = date.last_day_of_month().to_julian_days(); + + // Same day last year and last quarter (as julian days) + let d_same_day_ly = date.same_day_last_year().to_julian_days(); + let d_same_day_lq = date.same_day_last_quarter().to_julian_days(); + + // Current flags (relative to TODAYS_DATE) + // Note: Java has a bug where it compares julian days to day of month + // This will never be true, but we replicate the bug for compatibility + let d_current_day = d_date_sk == TODAYS_DATE.day() as i64; // Bug: comparing julian to day of month + let d_current_year = d_year == TODAYS_DATE.year(); + let d_current_month = d_current_year && d_moy == TODAYS_DATE.month(); + let d_current_quarter = d_current_year && d_qoy == CURRENT_QUARTER; + let d_current_week = d_current_year && d_week_seq == CURRENT_WEEK; + + // Create the row + let row = DateDimRow::new( + null_bit_map, + d_date_sk, + d_date_id, + date, + d_month_seq, + d_week_seq, + d_quarter_seq, + d_year, + d_dow, + d_moy, + d_dom, + d_qoy, + d_fy_year, + d_fy_quarter_seq, + d_fy_week_seq, + d_day_name, + d_quarter_name, + d_holiday, + d_weekend, + d_following_holiday, + d_first_dom, + d_last_dom, + d_same_day_ly, + d_same_day_lq, + d_current_day, + d_current_week, + d_current_month, + d_current_quarter, + d_current_year, + ); + + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.base.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.base + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/dbgen_version_row.rs b/tpcdsgen/src/row/dbgen_version_row.rs new file mode 100644 index 00000000..32c0d419 --- /dev/null +++ b/tpcdsgen/src/row/dbgen_version_row.rs @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::row::TableRow; + +/// DbgenVersion table row +#[derive(Debug, Clone)] +pub struct DbgenVersionRow { + null_bit_map: i64, + dv_version: String, + dv_create_date: String, + dv_create_time: String, + dv_cmdline_args: String, +} + +impl DbgenVersionRow { + pub fn new( + null_bit_map: i64, + dv_version: String, + dv_create_date: String, + dv_create_time: String, + dv_cmdline_args: String, + ) -> Self { + DbgenVersionRow { + null_bit_map, + dv_version, + dv_create_date, + dv_create_time, + dv_cmdline_args, + } + } + + /// Check if a column should be null based on the null bitmap + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_dv_version(&self) -> &str { + &self.dv_version + } + + pub fn get_dv_create_date(&self) -> &str { + &self.dv_create_date + } + + pub fn get_dv_create_time(&self) -> &str { + &self.dv_create_time + } + + pub fn get_dv_cmdline_args(&self) -> &str { + &self.dv_cmdline_args + } +} + +impl TableRow for DbgenVersionRow { + fn get_values(&self) -> Vec { + // Column positions match Java DbgenVersionGeneratorColumn (476-479) + vec![ + self.get_string_or_null(&self.dv_version, 0), + self.get_string_or_null(&self.dv_create_date, 1), + self.get_string_or_null(&self.dv_create_time, 2), + self.get_string_or_null(&self.dv_cmdline_args, 3), + ] + } +} diff --git a/tpcdsgen/src/row/dbgen_version_row_generator.rs b/tpcdsgen/src/row/dbgen_version_row_generator.rs new file mode 100644 index 00000000..231ad569 --- /dev/null +++ b/tpcdsgen/src/row/dbgen_version_row_generator.rs @@ -0,0 +1,91 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::config::Session; +use crate::error::Result; +use crate::row::{AbstractRowGenerator, DbgenVersionRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use chrono::Local; + +/// Row generator for the DBGEN_VERSION table (DbgenVersionRowGenerator) +pub struct DbgenVersionRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +/// DBGEN_VERSION constant from Java implementation +const DBGEN_VERSION: &str = "2.0.0"; + +impl Default for DbgenVersionRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl DbgenVersionRowGenerator { + /// Create a new DbgenVersionRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::DbgenVersion), + } + } + + /// Generate a DbgenVersionRow with current timestamp and version info + fn generate_dbgen_version_row( + &mut self, + _row_number: i64, + session: &Session, + ) -> Result { + // Get current date and time + let now = Local::now(); + + // Format date as "yyyy-MM-dd" (Java SimpleDateFormat equivalent) + let create_date = now.format("%Y-%m-%d").to_string(); + + // Format time as "HH:mm:ss" (Java SimpleDateFormat equivalent) + let create_time = now.format("%H:%M:%S").to_string(); + + // Get command line arguments from session + let cmdline_args = session.get_command_line_arguments(); + + Ok(DbgenVersionRow::new( + 0, // nullBitMap is always 0 for this table + DBGEN_VERSION.to_string(), + create_date, + create_time, + cmdline_args, + )) + } +} + +impl RowGenerator for DbgenVersionRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_dbgen_version_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/generated_row.rs b/tpcdsgen/src/row/generated_row.rs new file mode 100644 index 00000000..a281e806 --- /dev/null +++ b/tpcdsgen/src/row/generated_row.rs @@ -0,0 +1,310 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Generated row enum for static dispatch and zero-allocation row storage. +//! +//! This enum eliminates the need for `Box` in the hot path, +//! avoiding heap allocations and enabling static dispatch. +//! +//! See ISSUE-004 for details. + +use crate::row::{ + CallCenterRow, CatalogPageRow, CatalogReturnsRow, CatalogSalesRow, CustomerAddressRow, + CustomerDemographicsRow, CustomerRow, DateDimRow, DbgenVersionRow, HouseholdDemographicsRow, + IncomeBandRow, InventoryRow, ItemRow, PromotionRow, ReasonRow, ShipModeRow, StoreReturnsRow, + StoreRow, StoreSalesRow, TableRow, TimeDimRow, WarehouseRow, WebPageRow, WebReturnsRow, + WebSalesRow, WebSiteRow, +}; +use std::io::{self, Write}; + +/// Enum holding all possible generated row types. +/// +/// This enables static dispatch and eliminates heap allocations that would +/// be required with `Box`. +#[derive(Clone)] +pub enum GeneratedRow { + CallCenter(CallCenterRow), + CatalogPage(CatalogPageRow), + CatalogReturns(CatalogReturnsRow), + CatalogSales(CatalogSalesRow), + Customer(CustomerRow), + CustomerAddress(CustomerAddressRow), + CustomerDemographics(CustomerDemographicsRow), + DateDim(DateDimRow), + DbgenVersion(DbgenVersionRow), + HouseholdDemographics(HouseholdDemographicsRow), + IncomeBand(IncomeBandRow), + Inventory(InventoryRow), + Item(ItemRow), + Promotion(PromotionRow), + Reason(ReasonRow), + ShipMode(ShipModeRow), + Store(StoreRow), + StoreReturns(StoreReturnsRow), + StoreSales(StoreSalesRow), + TimeDim(TimeDimRow), + Warehouse(WarehouseRow), + WebPage(WebPageRow), + WebReturns(WebReturnsRow), + WebSales(WebSalesRow), + WebSite(WebSiteRow), +} + +impl TableRow for GeneratedRow { + fn get_values(&self) -> Vec { + match self { + GeneratedRow::CallCenter(row) => row.get_values(), + GeneratedRow::CatalogPage(row) => row.get_values(), + GeneratedRow::CatalogReturns(row) => row.get_values(), + GeneratedRow::CatalogSales(row) => row.get_values(), + GeneratedRow::Customer(row) => row.get_values(), + GeneratedRow::CustomerAddress(row) => row.get_values(), + GeneratedRow::CustomerDemographics(row) => row.get_values(), + GeneratedRow::DateDim(row) => row.get_values(), + GeneratedRow::DbgenVersion(row) => row.get_values(), + GeneratedRow::HouseholdDemographics(row) => row.get_values(), + GeneratedRow::IncomeBand(row) => row.get_values(), + GeneratedRow::Inventory(row) => row.get_values(), + GeneratedRow::Item(row) => row.get_values(), + GeneratedRow::Promotion(row) => row.get_values(), + GeneratedRow::Reason(row) => row.get_values(), + GeneratedRow::ShipMode(row) => row.get_values(), + GeneratedRow::Store(row) => row.get_values(), + GeneratedRow::StoreReturns(row) => row.get_values(), + GeneratedRow::StoreSales(row) => row.get_values(), + GeneratedRow::TimeDim(row) => row.get_values(), + GeneratedRow::Warehouse(row) => row.get_values(), + GeneratedRow::WebPage(row) => row.get_values(), + GeneratedRow::WebReturns(row) => row.get_values(), + GeneratedRow::WebSales(row) => row.get_values(), + GeneratedRow::WebSite(row) => row.get_values(), + } + } + + fn write_to(&self, writer: &mut dyn Write, separator: char) -> io::Result<()> { + match self { + GeneratedRow::CallCenter(row) => row.write_to(writer, separator), + GeneratedRow::CatalogPage(row) => row.write_to(writer, separator), + GeneratedRow::CatalogReturns(row) => row.write_to(writer, separator), + GeneratedRow::CatalogSales(row) => row.write_to(writer, separator), + GeneratedRow::Customer(row) => row.write_to(writer, separator), + GeneratedRow::CustomerAddress(row) => row.write_to(writer, separator), + GeneratedRow::CustomerDemographics(row) => row.write_to(writer, separator), + GeneratedRow::DateDim(row) => row.write_to(writer, separator), + GeneratedRow::DbgenVersion(row) => row.write_to(writer, separator), + GeneratedRow::HouseholdDemographics(row) => row.write_to(writer, separator), + GeneratedRow::IncomeBand(row) => row.write_to(writer, separator), + GeneratedRow::Inventory(row) => row.write_to(writer, separator), + GeneratedRow::Item(row) => row.write_to(writer, separator), + GeneratedRow::Promotion(row) => row.write_to(writer, separator), + GeneratedRow::Reason(row) => row.write_to(writer, separator), + GeneratedRow::ShipMode(row) => row.write_to(writer, separator), + GeneratedRow::Store(row) => row.write_to(writer, separator), + GeneratedRow::StoreReturns(row) => row.write_to(writer, separator), + GeneratedRow::StoreSales(row) => row.write_to(writer, separator), + GeneratedRow::TimeDim(row) => row.write_to(writer, separator), + GeneratedRow::Warehouse(row) => row.write_to(writer, separator), + GeneratedRow::WebPage(row) => row.write_to(writer, separator), + GeneratedRow::WebReturns(row) => row.write_to(writer, separator), + GeneratedRow::WebSales(row) => row.write_to(writer, separator), + GeneratedRow::WebSite(row) => row.write_to(writer, separator), + } + } +} + +// Convenience From implementations for easy conversion +impl From for GeneratedRow { + fn from(row: CallCenterRow) -> Self { + GeneratedRow::CallCenter(row) + } +} + +impl From for GeneratedRow { + fn from(row: CatalogPageRow) -> Self { + GeneratedRow::CatalogPage(row) + } +} + +impl From for GeneratedRow { + fn from(row: CatalogReturnsRow) -> Self { + GeneratedRow::CatalogReturns(row) + } +} + +impl From for GeneratedRow { + fn from(row: CatalogSalesRow) -> Self { + GeneratedRow::CatalogSales(row) + } +} + +impl From for GeneratedRow { + fn from(row: CustomerRow) -> Self { + GeneratedRow::Customer(row) + } +} + +impl From for GeneratedRow { + fn from(row: CustomerAddressRow) -> Self { + GeneratedRow::CustomerAddress(row) + } +} + +impl From for GeneratedRow { + fn from(row: CustomerDemographicsRow) -> Self { + GeneratedRow::CustomerDemographics(row) + } +} + +impl From for GeneratedRow { + fn from(row: DateDimRow) -> Self { + GeneratedRow::DateDim(row) + } +} + +impl From for GeneratedRow { + fn from(row: DbgenVersionRow) -> Self { + GeneratedRow::DbgenVersion(row) + } +} + +impl From for GeneratedRow { + fn from(row: HouseholdDemographicsRow) -> Self { + GeneratedRow::HouseholdDemographics(row) + } +} + +impl From for GeneratedRow { + fn from(row: IncomeBandRow) -> Self { + GeneratedRow::IncomeBand(row) + } +} + +impl From for GeneratedRow { + fn from(row: InventoryRow) -> Self { + GeneratedRow::Inventory(row) + } +} + +impl From for GeneratedRow { + fn from(row: ItemRow) -> Self { + GeneratedRow::Item(row) + } +} + +impl From for GeneratedRow { + fn from(row: PromotionRow) -> Self { + GeneratedRow::Promotion(row) + } +} + +impl From for GeneratedRow { + fn from(row: ReasonRow) -> Self { + GeneratedRow::Reason(row) + } +} + +impl From for GeneratedRow { + fn from(row: ShipModeRow) -> Self { + GeneratedRow::ShipMode(row) + } +} + +impl From for GeneratedRow { + fn from(row: StoreRow) -> Self { + GeneratedRow::Store(row) + } +} + +impl From for GeneratedRow { + fn from(row: StoreReturnsRow) -> Self { + GeneratedRow::StoreReturns(row) + } +} + +impl From for GeneratedRow { + fn from(row: StoreSalesRow) -> Self { + GeneratedRow::StoreSales(row) + } +} + +impl From for GeneratedRow { + fn from(row: TimeDimRow) -> Self { + GeneratedRow::TimeDim(row) + } +} + +impl From for GeneratedRow { + fn from(row: WarehouseRow) -> Self { + GeneratedRow::Warehouse(row) + } +} + +impl From for GeneratedRow { + fn from(row: WebPageRow) -> Self { + GeneratedRow::WebPage(row) + } +} + +impl From for GeneratedRow { + fn from(row: WebReturnsRow) -> Self { + GeneratedRow::WebReturns(row) + } +} + +impl From for GeneratedRow { + fn from(row: WebSalesRow) -> Self { + GeneratedRow::WebSales(row) + } +} + +impl From for GeneratedRow { + fn from(row: WebSiteRow) -> Self { + GeneratedRow::WebSite(row) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generated_row_from_call_center() { + let row = CallCenterRow::builder().build(); + let generated: GeneratedRow = row.into(); + assert!(matches!(generated, GeneratedRow::CallCenter(_))); + } + + #[test] + fn test_generated_row_get_values() { + let row = CallCenterRow::builder().build(); + let generated: GeneratedRow = row.clone().into(); + + // Values should be the same whether accessed directly or through enum + assert_eq!(row.get_values(), generated.get_values()); + } + + #[test] + fn test_generated_row_write_to() { + let row = CallCenterRow::builder().build(); + let generated: GeneratedRow = row.clone().into(); + + let mut buf1 = Vec::new(); + let mut buf2 = Vec::new(); + + row.write_to(&mut buf1, '|').unwrap(); + generated.write_to(&mut buf2, '|').unwrap(); + + assert_eq!(buf1, buf2); + } +} diff --git a/tpcdsgen/src/row/household_demographics_row.rs b/tpcdsgen/src/row/household_demographics_row.rs new file mode 100644 index 00000000..141539ab --- /dev/null +++ b/tpcdsgen/src/row/household_demographics_row.rs @@ -0,0 +1,252 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::row::TableRow; + +/// Household Demographics row data structure (HouseholdDemographicsRow) +/// Contains all fields for the HOUSEHOLD_DEMOGRAPHICS table in TPC-DS +#[derive(Debug, Clone, PartialEq)] +pub struct HouseholdDemographicsRow { + hd_demo_sk: i64, + hd_income_band_sk: i64, + hd_buy_potential: String, + hd_dep_count: i32, + hd_vehicle_count: i32, + null_bit_map: i64, +} + +impl HouseholdDemographicsRow { + /// Create a new builder for HouseholdDemographicsRow + pub fn builder() -> HouseholdDemographicsRowBuilder { + HouseholdDemographicsRowBuilder::new() + } + + // Getter methods (matching Java implementation) + pub fn get_hd_demo_sk(&self) -> i64 { + self.hd_demo_sk + } + + pub fn get_hd_income_band_sk(&self) -> i64 { + self.hd_income_band_sk + } + + pub fn get_hd_buy_potential(&self) -> &str { + &self.hd_buy_potential + } + + pub fn get_hd_dep_count(&self) -> i32 { + self.hd_dep_count + } + + pub fn get_hd_vehicle_count(&self) -> i32 { + self.hd_vehicle_count + } + + pub fn get_null_bit_map(&self) -> i64 { + self.null_bit_map + } + + /// Check if a field should be null based on the null bitmap + fn is_null(&self, column_position: i32) -> bool { + (self.null_bit_map & (1 << column_position)) != 0 + } + + /// Format a value as string, handling nulls + fn format_value(&self, value: &str, column_position: i32) -> String { + if self.is_null(column_position) { + "NULL".to_string() + } else { + value.to_string() + } + } + + /// Format a numeric value as string, handling nulls + fn format_numeric(&self, value: T, column_position: i32) -> String { + if self.is_null(column_position) { + "NULL".to_string() + } else { + value.to_string() + } + } +} + +impl TableRow for HouseholdDemographicsRow { + /// Get all values as strings for CSV output (getValues()) + fn get_values(&self) -> Vec { + vec![ + self.format_numeric(self.hd_demo_sk, 0), + self.format_numeric(self.hd_income_band_sk, 1), + self.format_value(&self.hd_buy_potential, 2), + self.format_numeric(self.hd_dep_count, 3), + self.format_numeric(self.hd_vehicle_count, 4), + ] + } +} + +/// Builder for HouseholdDemographicsRow (HouseholdDemographicsRow.Builder) +#[derive(Debug, Default)] +pub struct HouseholdDemographicsRowBuilder { + hd_demo_sk: Option, + hd_income_band_sk: Option, + hd_buy_potential: Option, + hd_dep_count: Option, + hd_vehicle_count: Option, + null_bit_map: Option, +} + +impl HouseholdDemographicsRowBuilder { + pub fn new() -> Self { + Self::default() + } + + // Setter methods (matching Java builder pattern) + pub fn set_hd_demo_sk(mut self, value: i64) -> Self { + self.hd_demo_sk = Some(value); + self + } + + pub fn set_hd_income_band_sk(mut self, value: i64) -> Self { + self.hd_income_band_sk = Some(value); + self + } + + pub fn set_hd_buy_potential(mut self, value: String) -> Self { + self.hd_buy_potential = Some(value); + self + } + + pub fn set_hd_dep_count(mut self, value: i32) -> Self { + self.hd_dep_count = Some(value); + self + } + + pub fn set_hd_vehicle_count(mut self, value: i32) -> Self { + self.hd_vehicle_count = Some(value); + self + } + + pub fn set_null_bit_map(mut self, value: i64) -> Self { + self.null_bit_map = Some(value); + self + } + + /// Build the HouseholdDemographicsRow + pub fn build(self) -> HouseholdDemographicsRow { + HouseholdDemographicsRow { + hd_demo_sk: self.hd_demo_sk.unwrap_or(0), + hd_income_band_sk: self.hd_income_band_sk.unwrap_or(0), + hd_buy_potential: self.hd_buy_potential.unwrap_or_default(), + hd_dep_count: self.hd_dep_count.unwrap_or(0), + hd_vehicle_count: self.hd_vehicle_count.unwrap_or(0), + null_bit_map: self.null_bit_map.unwrap_or(0), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_household_demographics_row_builder() { + let row = HouseholdDemographicsRow::builder() + .set_hd_demo_sk(1) + .set_hd_income_band_sk(5) + .set_hd_buy_potential("1001-5000".to_string()) + .set_hd_dep_count(3) + .set_hd_vehicle_count(2) + .set_null_bit_map(0) + .build(); + + // Test getters + assert_eq!(row.get_hd_demo_sk(), 1); + assert_eq!(row.get_hd_income_band_sk(), 5); + assert_eq!(row.get_hd_buy_potential(), "1001-5000"); + assert_eq!(row.get_hd_dep_count(), 3); + assert_eq!(row.get_hd_vehicle_count(), 2); + assert_eq!(row.get_null_bit_map(), 0); + } + + #[test] + fn test_household_demographics_row_table_row() { + let row = HouseholdDemographicsRow::builder() + .set_hd_demo_sk(1) + .set_hd_income_band_sk(5) + .set_hd_buy_potential("1001-5000".to_string()) + .set_hd_dep_count(3) + .set_hd_vehicle_count(2) + .set_null_bit_map(0) + .build(); + + let values = row.get_values(); + assert_eq!(values.len(), 5); // 5 columns total + assert_eq!(values[0], "1"); // hd_demo_sk + assert_eq!(values[1], "5"); // hd_income_band_sk + assert_eq!(values[2], "1001-5000"); // hd_buy_potential + assert_eq!(values[3], "3"); // hd_dep_count + assert_eq!(values[4], "2"); // hd_vehicle_count + } + + #[test] + fn test_household_demographics_row_clone_and_equality() { + let row1 = HouseholdDemographicsRow::builder() + .set_hd_demo_sk(42) + .set_hd_buy_potential("501-1000".to_string()) + .set_hd_dep_count(1) + .build(); + + let row2 = row1.clone(); + assert_eq!(row1, row2); + assert_eq!(row1.get_hd_demo_sk(), row2.get_hd_demo_sk()); + assert_eq!(row1.get_hd_buy_potential(), row2.get_hd_buy_potential()); + } + + #[test] + fn test_builder_chaining() { + // Test that builder methods can be chained in any order + let row = HouseholdDemographicsRow::builder() + .set_hd_vehicle_count(1) + .set_hd_demo_sk(100) + .set_hd_dep_count(0) + .set_hd_income_band_sk(3) + .set_hd_buy_potential("Unknown".to_string()) + .build(); + + assert_eq!(row.get_hd_demo_sk(), 100); + assert_eq!(row.get_hd_income_band_sk(), 3); + assert_eq!(row.get_hd_buy_potential(), "Unknown"); + assert_eq!(row.get_hd_dep_count(), 0); + assert_eq!(row.get_hd_vehicle_count(), 1); + } + + #[test] + fn test_null_handling() { + // Test null bitmap handling - set bit 2 (hd_buy_potential) + let row = HouseholdDemographicsRow::builder() + .set_hd_demo_sk(1) + .set_hd_income_band_sk(5) + .set_hd_buy_potential("1001-5000".to_string()) + .set_hd_dep_count(3) + .set_hd_vehicle_count(2) + .set_null_bit_map(1 << 2) // Make hd_buy_potential null + .build(); + + let values = row.get_values(); + assert_eq!(values[0], "1"); // hd_demo_sk not null + assert_eq!(values[1], "5"); // hd_income_band_sk not null + assert_eq!(values[2], "NULL"); // hd_buy_potential is null + assert_eq!(values[3], "3"); // hd_dep_count not null + assert_eq!(values[4], "2"); // hd_vehicle_count not null + } +} diff --git a/tpcdsgen/src/row/household_demographics_row_generator.rs b/tpcdsgen/src/row/household_demographics_row_generator.rs new file mode 100644 index 00000000..e7ba45c3 --- /dev/null +++ b/tpcdsgen/src/row/household_demographics_row_generator.rs @@ -0,0 +1,118 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::config::Session; +use crate::distribution::DemographicsDistributions; +use crate::error::Result; +use crate::generator::HouseholdDemographicsGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{ + AbstractRowGenerator, HouseholdDemographicsRow, RowGenerator, RowGeneratorResult, +}; +use crate::table::Table; + +/// Row generator for the HOUSEHOLD_DEMOGRAPHICS table (HouseholdDemographicsRowGenerator) +pub struct HouseholdDemographicsRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for HouseholdDemographicsRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl HouseholdDemographicsRowGenerator { + /// Create a new HouseholdDemographicsRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::HouseholdDemographics), + } + } + + /// Generate a HouseholdDemographicsRow with realistic data following Java implementation + fn generate_household_demographics_row( + &mut self, + row_number: i64, + _session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&HouseholdDemographicsGeneratorColumn::HdNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = + RandomValueGenerator::generate_uniform_random_key(1, i32::MAX as i64, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::HouseholdDemographics.get_null_basis_points() { + bit_map & !Table::HouseholdDemographics.get_not_null_bit_map() + } else { + 0 + }; + + // Generate household demographics using index-based cartesian product (algorithm from Java) + let hd_demo_sk = row_number; + let mut index = hd_demo_sk; + + // Get income band id using modulo + let hd_income_band_sk = + (index % DemographicsDistributions::get_income_band_size() as i64) + 1; + index /= DemographicsDistributions::get_income_band_size() as i64; + + // Get buy potential and divide index + let hd_buy_potential = + DemographicsDistributions::get_buy_potential_for_index_mod_size(index); + index /= DemographicsDistributions::get_buy_potential_size() as i64; + + // Get dependent count and divide index + let hd_dep_count = DemographicsDistributions::get_dep_count_for_index_mod_size(index); + index /= DemographicsDistributions::get_dep_count_size() as i64; + + // Get vehicle count (no division needed, last in sequence) + let hd_vehicle_count = + DemographicsDistributions::get_vehicle_count_for_index_mod_size(index); + + Ok(HouseholdDemographicsRow::builder() + .set_hd_demo_sk(hd_demo_sk) + .set_hd_income_band_sk(hd_income_band_sk) + .set_hd_buy_potential(hd_buy_potential.to_string()) + .set_hd_dep_count(hd_dep_count) + .set_hd_vehicle_count(hd_vehicle_count) + .set_null_bit_map(null_bit_map) + .build()) + } +} + +impl RowGenerator for HouseholdDemographicsRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_household_demographics_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/income_band_row.rs b/tpcdsgen/src/row/income_band_row.rs new file mode 100644 index 00000000..136f0ee5 --- /dev/null +++ b/tpcdsgen/src/row/income_band_row.rs @@ -0,0 +1,64 @@ +use crate::row::TableRow; + +/// Income band table row (IncomeBandRow) +#[derive(Debug, Clone)] +pub struct IncomeBandRow { + null_bit_map: i64, + ib_income_band_id: i32, + ib_lower_bound: i32, + ib_upper_bound: i32, +} + +impl IncomeBandRow { + pub fn new( + null_bit_map: i64, + ib_income_band_id: i32, + ib_lower_bound: i32, + ib_upper_bound: i32, + ) -> Self { + IncomeBandRow { + null_bit_map, + ib_income_band_id, + ib_lower_bound, + ib_upper_bound, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_ib_income_band_id(&self) -> i32 { + self.ib_income_band_id + } + + pub fn get_ib_lower_bound(&self) -> i32 { + self.ib_lower_bound + } + + pub fn get_ib_upper_bound(&self) -> i32 { + self.ib_upper_bound + } +} + +impl TableRow for IncomeBandRow { + fn get_values(&self) -> Vec { + // Column positions match Java IncomeBandGeneratorColumn + // First column (IB_INCOME_BAND_ID) is at global position 194, so relative positions are 0-2 + vec![ + self.get_string_or_null(self.ib_income_band_id, 0), + self.get_string_or_null(self.ib_lower_bound, 1), + self.get_string_or_null(self.ib_upper_bound, 2), + ] + } +} diff --git a/tpcdsgen/src/row/income_band_row_generator.rs b/tpcdsgen/src/row/income_band_row_generator.rs new file mode 100644 index 00000000..06c7bebc --- /dev/null +++ b/tpcdsgen/src/row/income_band_row_generator.rs @@ -0,0 +1,86 @@ +use crate::config::Session; +use crate::distribution::DemographicsDistributions; +use crate::error::Result; +use crate::generator::IncomeBandGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, IncomeBandRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; + +/// Row generator for the INCOME_BAND table (IncomeBandRowGenerator) +pub struct IncomeBandRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for IncomeBandRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl IncomeBandRowGenerator { + /// Create a new IncomeBandRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::IncomeBand), + } + } + + /// Generate an IncomeBandRow with realistic data following Java implementation + fn generate_income_band_row( + &mut self, + row_number: i64, + _session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&IncomeBandGeneratorColumn::IbNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = + RandomValueGenerator::generate_uniform_random_key(1, i32::MAX as i64, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::IncomeBand.get_null_basis_points() { + bit_map & !Table::IncomeBand.get_not_null_bit_map() + } else { + 0 + }; + + let ib_income_band_id = row_number as i32; + let ib_lower_bound = DemographicsDistributions::get_income_band_lower_bound_at_index( + (row_number - 1) as usize, + )?; + let ib_upper_bound = DemographicsDistributions::get_income_band_upper_bound_at_index( + (row_number - 1) as usize, + )?; + + Ok(IncomeBandRow::new( + null_bit_map, + ib_income_band_id, + ib_lower_bound, + ib_upper_bound, + )) + } +} + +impl RowGenerator for IncomeBandRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_income_band_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/inventory_row.rs b/tpcdsgen/src/row/inventory_row.rs new file mode 100644 index 00000000..b4b070e0 --- /dev/null +++ b/tpcdsgen/src/row/inventory_row.rs @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Inventory row data structure + +use crate::generator::{GeneratorColumn, InventoryGeneratorColumn}; +use crate::row::TableRow; + +/// Represents a single row in the inventory table. +#[derive(Clone)] +pub struct InventoryRow { + null_bit_map: i64, + inv_date_sk: i64, + inv_item_sk: i64, + inv_warehouse_sk: i64, + inv_quantity_on_hand: i32, +} + +impl InventoryRow { + pub fn new( + null_bit_map: i64, + inv_date_sk: i64, + inv_item_sk: i64, + inv_warehouse_sk: i64, + inv_quantity_on_hand: i32, + ) -> Self { + InventoryRow { + null_bit_map, + inv_date_sk, + inv_item_sk, + inv_warehouse_sk, + inv_quantity_on_hand, + } + } + + fn get_string_or_null_for_key(&self, value: i64, column: InventoryGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null(&self, value: i32, column: InventoryGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn is_null_at(&self, column: InventoryGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - InventoryGeneratorColumn::InvDateSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } +} + +impl TableRow for InventoryRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null_for_key(self.inv_date_sk, InventoryGeneratorColumn::InvDateSk), + self.get_string_or_null_for_key(self.inv_item_sk, InventoryGeneratorColumn::InvItemSk), + self.get_string_or_null_for_key( + self.inv_warehouse_sk, + InventoryGeneratorColumn::InvWarehouseSk, + ), + self.get_string_or_null( + self.inv_quantity_on_hand, + InventoryGeneratorColumn::InvQuantityOnHand, + ), + ] + } +} diff --git a/tpcdsgen/src/row/inventory_row_generator.rs b/tpcdsgen/src/row/inventory_row_generator.rs new file mode 100644 index 00000000..9b4293d3 --- /dev/null +++ b/tpcdsgen/src/row/inventory_row_generator.rs @@ -0,0 +1,121 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Inventory row generator +//! +//! The inventory table represents weekly snapshots of item inventory levels +//! at each warehouse. It's a cross-join of items x warehouses x weeks. + +use crate::config::Session; +use crate::error::Result; +use crate::generator::InventoryGeneratorColumn; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::inventory_row::InventoryRow; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::match_surrogate_key; +use crate::table::Table; +use crate::types::Date; + +pub struct InventoryRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl InventoryRowGenerator { + pub fn new() -> Self { + InventoryRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::Inventory), + } + } +} + +impl Default for InventoryRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for InventoryRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + use InventoryGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&InvNulls); + let null_bit_map = create_null_bit_map(Table::Inventory, stream); + + // Decode the row number into item, warehouse, and date indices + // This is a cross-join: item x warehouse x date + let mut index = row_number - 1; + + // Get item count (unique item IDs, not row count since Item keeps history) + let item_count = scaling.get_id_count(crate::config::Table::Item); + + // Item cycles fastest + let inv_item_sk_unique = (index % item_count) + 1; + index /= item_count; + + // Get warehouse count + let warehouse_count = scaling.get_row_count(crate::config::Table::Warehouse); + + // Warehouse cycles next + let inv_warehouse_sk = (index % warehouse_count) + 1; + index /= warehouse_count; + + // Date cycles slowest - inventory is updated weekly + let inv_date_sk = Date::JULIAN_DATE_MINIMUM as i64 + (index * 7); + + // The join between item and inventory is tricky. The item_id selected above identifies + // a unique part num but item is a slowly changing dimension, so we need to account for + // that in selecting the surrogate key to join with + let inv_item_sk = match_surrogate_key( + inv_item_sk_unique, + inv_date_sk, + crate::config::Table::Item, + scaling, + ); + + // Generate random quantity on hand (0-1000) + let stream = self + .abstract_generator + .get_random_number_stream(&InvQuantityOnHand); + let inv_quantity_on_hand = + RandomValueGenerator::generate_uniform_random_int(0, 1000, stream); + + let row = InventoryRow::new( + null_bit_map, + inv_date_sk, + inv_item_sk, + inv_warehouse_sk, + inv_quantity_on_hand, + ); + + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, _starting_row_number: i64) { + // Inventory doesn't need special skip logic as it's not order-based + } +} diff --git a/tpcdsgen/src/row/item_row.rs b/tpcdsgen/src/row/item_row.rs new file mode 100644 index 00000000..41ed8cb0 --- /dev/null +++ b/tpcdsgen/src/row/item_row.rs @@ -0,0 +1,199 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Item row structure and formatting + +use crate::generator::{GeneratorColumn, ItemGeneratorColumn}; +use crate::row::TableRow; +use crate::types::{Date, Decimal}; + +/// Item row +#[derive(Clone)] +pub struct ItemRow { + null_bit_map: i64, + i_item_sk: i64, + i_item_id: String, + i_rec_start_date_id: i64, + i_rec_end_date_id: i64, + i_item_desc: String, + i_current_price: Decimal, + i_wholesale_cost: Decimal, + i_brand_id: i64, + i_brand: String, + i_class_id: i64, + i_class: String, + i_category_id: i64, + i_category: String, + i_manufact_id: i64, + i_manufact: String, + i_size: String, + i_formulation: String, + i_color: String, + i_units: String, + i_container: String, + i_manager_id: i64, + i_product_name: String, +} + +impl ItemRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + i_item_sk: i64, + i_item_id: String, + i_rec_start_date_id: i64, + i_rec_end_date_id: i64, + i_item_desc: String, + i_current_price: Decimal, + i_wholesale_cost: Decimal, + i_brand_id: i64, + i_brand: String, + i_class_id: i64, + i_class: String, + i_category_id: i64, + i_category: String, + i_manufact_id: i64, + i_manufact: String, + i_size: String, + i_formulation: String, + i_color: String, + i_units: String, + i_container: String, + i_manager_id: i64, + i_product_name: String, + ) -> Self { + ItemRow { + null_bit_map, + i_item_sk, + i_item_id, + i_rec_start_date_id, + i_rec_end_date_id, + i_item_desc, + i_current_price, + i_wholesale_cost, + i_brand_id, + i_brand, + i_class_id, + i_class, + i_category_id, + i_category, + i_manufact_id, + i_manufact, + i_size, + i_formulation, + i_color, + i_units, + i_container, + i_manager_id, + i_product_name, + } + } + + fn is_null(&self, column: &ItemGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - ItemGeneratorColumn::IItemSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key(&self, value: i64, column: &ItemGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null(&self, value: &str, column: &ItemGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_decimal_or_null(&self, value: &Decimal, column: &ItemGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_date_string_or_null(&self, julian_days: i64, column: &ItemGeneratorColumn) -> String { + if self.is_null(column) || julian_days < 0 { + String::new() + } else { + Date::from_julian_days(julian_days as i32).to_string() + } + } + + // Getters for SCD fields + pub fn get_i_item_desc(&self) -> &str { + &self.i_item_desc + } + + pub fn get_i_wholesale_cost(&self) -> Decimal { + self.i_wholesale_cost + } + + pub fn get_i_brand_id(&self) -> i64 { + self.i_brand_id + } + + pub fn get_i_class_id(&self) -> i64 { + self.i_class_id + } + + pub fn get_i_manufact_id(&self) -> i64 { + self.i_manufact_id + } + + pub fn get_i_manufact(&self) -> &str { + &self.i_manufact + } + + pub fn get_i_formulation(&self) -> &str { + &self.i_formulation + } +} + +impl TableRow for ItemRow { + fn get_values(&self) -> Vec { + use ItemGeneratorColumn::*; + vec![ + self.get_string_or_null_for_key(self.i_item_sk, &IItemSk), + self.get_string_or_null(&self.i_item_id, &IItemId), + self.get_date_string_or_null(self.i_rec_start_date_id, &IRecStartDateId), + self.get_date_string_or_null(self.i_rec_end_date_id, &IRecEndDateId), + self.get_string_or_null(&self.i_item_desc, &IItemDesc), + self.get_decimal_or_null(&self.i_current_price, &ICurrentPrice), + self.get_decimal_or_null(&self.i_wholesale_cost, &IWholesaleCost), + self.get_string_or_null_for_key(self.i_brand_id, &IBrandId), + self.get_string_or_null(&self.i_brand, &IBrand), + self.get_string_or_null_for_key(self.i_class_id, &IClassId), + self.get_string_or_null(&self.i_class, &IClass), + self.get_string_or_null_for_key(self.i_category_id, &ICategoryId), + self.get_string_or_null(&self.i_category, &ICategory), + self.get_string_or_null_for_key(self.i_manufact_id, &IManufactId), + self.get_string_or_null(&self.i_manufact, &IManufact), + self.get_string_or_null(&self.i_size, &ISize), + self.get_string_or_null(&self.i_formulation, &IFormulation), + self.get_string_or_null(&self.i_color, &IColor), + self.get_string_or_null(&self.i_units, &IUnits), + self.get_string_or_null(&self.i_container, &IContainer), + self.get_string_or_null_for_key(self.i_manager_id, &IManagerId), + self.get_string_or_null(&self.i_product_name, &IProductName), + ] + } +} diff --git a/tpcdsgen/src/row/item_row_generator.rs b/tpcdsgen/src/row/item_row_generator.rs new file mode 100644 index 00000000..d1809df8 --- /dev/null +++ b/tpcdsgen/src/row/item_row_generator.rs @@ -0,0 +1,366 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Item row generator (Slowly Changing Dimension) + +use crate::config::Session; +use crate::distribution::{ + get_brand_syllables_distribution, get_category_at_index, get_has_size_at_index, + pick_random_category_class, pick_random_category_index, pick_random_color, + pick_random_current_price_range, pick_random_manager_id_range, pick_random_manufact_id_range, + pick_random_size, pick_random_unit, ColorsWeights, IdWeights, SizeWeights, +}; +use crate::error::Result; +use crate::generator::ItemGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::item_row::ItemRow; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::{ + compute_scd_key, get_value_for_slowly_changing_dimension, +}; +use crate::table::Table; +use crate::types::Decimal; + +fn min_item_markdown_pct() -> Decimal { + Decimal::new(30, 2).unwrap() +} + +fn max_item_markdown_pct() -> Decimal { + Decimal::new(90, 2).unwrap() +} +const ROW_SIZE_I_PRODUCT_NAME: i32 = 50; +const ROW_SIZE_I_ITEM_DESC: i32 = 200; +const ROW_SIZE_I_MANUFACT: i32 = 50; +const ROW_SIZE_I_FORMULATION: i32 = 20; +#[allow(dead_code)] +const I_PROMO_PERCENTAGE: i32 = 20; + +pub struct ItemRowGenerator { + abstract_generator: AbstractRowGenerator, + previous_row: Option, +} + +impl ItemRowGenerator { + pub fn new() -> Self { + ItemRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::Item), + previous_row: None, + } + } + + fn generate_item_row(&mut self, row_number: i64, session: &Session) -> Result { + use ItemGeneratorColumn::*; + + // Generate null bit map first + let stream = self.abstract_generator.get_random_number_stream(&INulls); + let null_bit_map = create_null_bit_map(Table::Item, stream); + + let i_item_sk = row_number; + + // Generate manager ID range + let stream = self + .abstract_generator + .get_random_number_stream(&IManagerId); + let (manager_min, manager_max) = pick_random_manager_id_range(IdWeights::Unified, stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&IManagerId); + let i_manager_id = RandomValueGenerator::generate_uniform_random_key( + manager_min as i64, + manager_max as i64, + stream, + ); + + // Compute SCD key + let scd_key = compute_scd_key(Table::Item, row_number); + let i_item_id = scd_key.get_business_key().to_string(); + let i_rec_start_date_id = scd_key.get_start_date(); + let i_rec_end_date_id = scd_key.get_end_date(); + let is_new_business_key = scd_key.is_new_business_key(); + + // Get field change flags + let stream = self.abstract_generator.get_random_number_stream(&IScd); + let mut field_change_flags = stream.next_random() as i32; + + // Generate item description + let stream = self.abstract_generator.get_random_number_stream(&IItemDesc); + let mut i_item_desc = + RandomValueGenerator::generate_random_text(1, ROW_SIZE_I_ITEM_DESC, stream); + if let Some(ref prev_row) = self.previous_row { + i_item_desc = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_item_desc().to_string(), + i_item_desc, + ); + } + field_change_flags >>= 1; + + // Generate current price - There is a bug in C code that always chooses new record + let stream = self + .abstract_generator + .get_random_number_stream(&ICurrentPrice); + let (price_min, price_max) = pick_random_current_price_range(stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&ICurrentPrice); + let i_current_price = + RandomValueGenerator::generate_uniform_random_decimal(price_min, price_max, stream); + field_change_flags >>= 1; + + // Generate wholesale cost + let stream = self + .abstract_generator + .get_random_number_stream(&IWholesaleCost); + let markdown = RandomValueGenerator::generate_uniform_random_decimal( + min_item_markdown_pct(), + max_item_markdown_pct(), + stream, + ); + let mut i_wholesale_cost = Decimal::multiply(i_current_price, markdown); + if let Some(ref prev_row) = self.previous_row { + i_wholesale_cost = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_wholesale_cost(), + i_wholesale_cost, + ); + } + field_change_flags >>= 1; + + // Generate category + let stream = self.abstract_generator.get_random_number_stream(&ICategory); + let i_category_index = pick_random_category_index(stream)?; + let i_category_id = (i_category_index + 1) as i64; + let i_category = get_category_at_index(i_category_index).to_string(); + + // Generate class + let stream = self.abstract_generator.get_random_number_stream(&IClass); + let category_class = pick_random_category_class(i_category_index, stream)?; + let i_class = category_class.get_name().to_string(); + let new_class_id = category_class.get_id(); + let mut i_class_id = new_class_id; + if let Some(ref prev_row) = self.previous_row { + i_class_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_class_id(), + i_class_id, + ); + } + field_change_flags >>= 1; + + // Generate brand + let brand_count = category_class.get_brand_count(); + let i_brand_id_base = row_number % brand_count as i64 + 1; + let i_brand = format!( + "{} #{}", + RandomValueGenerator::generate_word( + i_category_id * 10 + new_class_id, + 45, + get_brand_syllables_distribution(), + ), + i_brand_id_base + ); + let mut i_brand_id = i_brand_id_base + (i_category_id * 1000 + new_class_id) * 1000; + if let Some(ref prev_row) = self.previous_row { + i_brand_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_brand_id(), + i_brand_id, + ); + } + field_change_flags >>= 1; + + // Generate size - always uses new value due to bug in C code + let has_size = get_has_size_at_index(i_category_index); + let stream = self.abstract_generator.get_random_number_stream(&ISize); + let i_size = pick_random_size( + if has_size == 0 { + SizeWeights::NoSize + } else { + SizeWeights::Sized + }, + stream, + )?; + field_change_flags >>= 1; + + // Generate manufact ID + let stream = self + .abstract_generator + .get_random_number_stream(&IManufactId); + let (manufact_min, manufact_max) = + pick_random_manufact_id_range(IdWeights::Unified, stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&IManufactId); + let mut i_manufact_id = + RandomValueGenerator::generate_uniform_random_int(manufact_min, manufact_max, stream) + as i64; + if let Some(ref prev_row) = self.previous_row { + i_manufact_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_manufact_id(), + i_manufact_id, + ); + } + field_change_flags >>= 1; + + // Generate manufact name + let mut i_manufact = RandomValueGenerator::generate_word( + i_manufact_id, + ROW_SIZE_I_MANUFACT, + crate::distribution::get_syllables_distribution(), + ); + if let Some(ref prev_row) = self.previous_row { + i_manufact = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_manufact().to_string(), + i_manufact, + ); + } + field_change_flags >>= 1; + + // Generate formulation + let stream = self + .abstract_generator + .get_random_number_stream(&IFormulation); + let mut i_formulation = RandomValueGenerator::generate_random_charset( + RandomValueGenerator::DIGITS, + ROW_SIZE_I_FORMULATION, + ROW_SIZE_I_FORMULATION, + stream, + ); + let stream = self + .abstract_generator + .get_random_number_stream(&IFormulation); + let color = pick_random_color(ColorsWeights::Skewed, stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&IFormulation); + let position = RandomValueGenerator::generate_uniform_random_int( + 0, + i_formulation.len() as i32 - color.len() as i32 - 1, + stream, + ) as usize; + // Replace part of formulation with color + let mut formulation_chars: Vec = i_formulation.chars().collect(); + for (i, c) in color.chars().enumerate() { + if position + i < formulation_chars.len() { + formulation_chars[position + i] = c; + } + } + i_formulation = formulation_chars.into_iter().collect(); + if let Some(ref prev_row) = self.previous_row { + i_formulation = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_i_formulation().to_string(), + i_formulation, + ); + } + + // These fields always use new value due to bug in C code + let stream = self.abstract_generator.get_random_number_stream(&IColor); + let i_color = pick_random_color(ColorsWeights::Skewed, stream)?; + + let stream = self.abstract_generator.get_random_number_stream(&IUnits); + let i_units = pick_random_unit(stream)?; + + let i_container = "Unknown".to_string(); + + let i_product_name = RandomValueGenerator::generate_word( + row_number, + ROW_SIZE_I_PRODUCT_NAME, + crate::distribution::get_syllables_distribution(), + ); + + // Generate promo sk + // Note: i_promo_sk is generated but not included in output (matches Java behavior) + // We still need to generate it to consume the random seeds + let stream = self.abstract_generator.get_random_number_stream(&IPromoSk); + let _i_promo_sk = generate_join_key( + &IPromoSk, + stream, + crate::config::Table::Promotion, + 1, + session.get_scaling(), + )?; + let stream = self.abstract_generator.get_random_number_stream(&IPromoSk); + let _temp = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + + let row = ItemRow::new( + null_bit_map, + i_item_sk, + i_item_id, + i_rec_start_date_id, + i_rec_end_date_id, + i_item_desc, + i_current_price, + i_wholesale_cost, + i_brand_id, + i_brand, + i_class_id, + i_class, + i_category_id, + i_category, + i_manufact_id, + i_manufact, + i_size, + i_formulation, + i_color, + i_units, + i_container, + i_manager_id, + i_product_name, + ); + + Ok(row) + } +} + +impl Default for ItemRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for ItemRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_item_row(row_number, session)?; + // Store for SCD logic on next row + self.previous_row = Some(row.clone()); + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/mod.rs b/tpcdsgen/src/row/mod.rs new file mode 100644 index 00000000..dc264aeb --- /dev/null +++ b/tpcdsgen/src/row/mod.rs @@ -0,0 +1,109 @@ +pub mod abstract_row_generator; +pub mod call_center_row; +pub mod call_center_row_generator; +pub mod catalog_page_row; +pub mod catalog_page_row_generator; +pub mod catalog_returns_row; +pub mod catalog_returns_row_generator; +pub mod catalog_sales_row; +pub mod catalog_sales_row_generator; +pub mod customer_address_row; +pub mod customer_address_row_generator; +pub mod customer_demographics_row; +pub mod customer_demographics_row_generator; +pub mod customer_row; +pub mod customer_row_generator; +pub mod date_dim_row; +pub mod date_dim_row_generator; +pub mod dbgen_version_row; +pub mod dbgen_version_row_generator; +pub mod generated_row; +pub mod household_demographics_row; +pub mod household_demographics_row_generator; +pub mod income_band_row; +pub mod income_band_row_generator; +pub mod inventory_row; +pub mod inventory_row_generator; +pub mod item_row; +pub mod item_row_generator; +pub mod promotion_row; +pub mod promotion_row_generator; +pub mod reason_row; +pub mod reason_row_generator; +pub mod row_generator; +pub mod ship_mode_row; +pub mod ship_mode_row_generator; +pub mod store_returns_row; +pub mod store_returns_row_generator; +pub mod store_row; +pub mod store_row_generator; +pub mod store_sales_row; +pub mod store_sales_row_generator; +pub mod table_row; +pub mod time_dim_row; +pub mod time_dim_row_generator; +pub mod warehouse_row; +pub mod warehouse_row_generator; +pub mod web_page_row; +pub mod web_page_row_generator; +pub mod web_returns_row; +pub mod web_returns_row_generator; +pub mod web_sales_row; +pub mod web_sales_row_generator; +pub mod web_site_row; +pub mod web_site_row_generator; + +pub use abstract_row_generator::AbstractRowGenerator; +pub use call_center_row::CallCenterRow; +pub use call_center_row_generator::CallCenterRowGenerator; +pub use catalog_page_row::CatalogPageRow; +pub use catalog_page_row_generator::CatalogPageRowGenerator; +pub use catalog_returns_row::CatalogReturnsRow; +pub use catalog_returns_row_generator::CatalogReturnsRowGenerator; +pub use catalog_sales_row::CatalogSalesRow; +pub use catalog_sales_row_generator::CatalogSalesRowGenerator; +pub use customer_address_row::CustomerAddressRow; +pub use customer_address_row_generator::CustomerAddressRowGenerator; +pub use customer_demographics_row::CustomerDemographicsRow; +pub use customer_demographics_row_generator::CustomerDemographicsRowGenerator; +pub use customer_row::CustomerRow; +pub use customer_row_generator::CustomerRowGenerator; +pub use date_dim_row::DateDimRow; +pub use date_dim_row_generator::DateDimRowGenerator; +pub use dbgen_version_row::DbgenVersionRow; +pub use dbgen_version_row_generator::DbgenVersionRowGenerator; +pub use generated_row::GeneratedRow; +pub use household_demographics_row::HouseholdDemographicsRow; +pub use household_demographics_row_generator::HouseholdDemographicsRowGenerator; +pub use income_band_row::IncomeBandRow; +pub use income_band_row_generator::IncomeBandRowGenerator; +pub use inventory_row::InventoryRow; +pub use inventory_row_generator::InventoryRowGenerator; +pub use item_row::ItemRow; +pub use item_row_generator::ItemRowGenerator; +pub use promotion_row::PromotionRow; +pub use promotion_row_generator::PromotionRowGenerator; +pub use reason_row::ReasonRow; +pub use reason_row_generator::ReasonRowGenerator; +pub use row_generator::{RowGenerator, RowGeneratorResult}; +pub use ship_mode_row::ShipModeRow; +pub use ship_mode_row_generator::ShipModeRowGenerator; +pub use store_returns_row::StoreReturnsRow; +pub use store_returns_row_generator::StoreReturnsRowGenerator; +pub use store_row::StoreRow; +pub use store_row_generator::StoreRowGenerator; +pub use store_sales_row::StoreSalesRow; +pub use store_sales_row_generator::StoreSalesRowGenerator; +pub use table_row::TableRow; +pub use time_dim_row::TimeDimRow; +pub use time_dim_row_generator::TimeDimRowGenerator; +pub use warehouse_row::WarehouseRow; +pub use warehouse_row_generator::WarehouseRowGenerator; +pub use web_page_row::WebPageRow; +pub use web_page_row_generator::WebPageRowGenerator; +pub use web_returns_row::WebReturnsRow; +pub use web_returns_row_generator::WebReturnsRowGenerator; +pub use web_sales_row::WebSalesRow; +pub use web_sales_row_generator::WebSalesRowGenerator; +pub use web_site_row::WebSiteRow; +pub use web_site_row_generator::WebSiteRowGenerator; diff --git a/tpcdsgen/src/row/promotion_row.rs b/tpcdsgen/src/row/promotion_row.rs new file mode 100644 index 00000000..302e5db7 --- /dev/null +++ b/tpcdsgen/src/row/promotion_row.rs @@ -0,0 +1,281 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::generator::{GeneratorColumn, PromotionGeneratorColumn}; +use crate::row::TableRow; +use crate::types::Decimal; + +#[derive(Debug, Clone, PartialEq)] +pub struct PromotionRow { + null_bit_map: i64, + p_promo_sk: i64, + p_promo_id: String, + p_start_date_id: i64, + p_end_date_id: i64, + p_item_sk: i64, + p_cost: Decimal, + p_response_target: i32, + p_promo_name: String, + p_channel_dmail: bool, + p_channel_email: bool, + p_channel_catalog: bool, + p_channel_tv: bool, + p_channel_radio: bool, + p_channel_press: bool, + p_channel_event: bool, + p_channel_demo: bool, + p_channel_details: String, + p_purpose: String, + p_discount_active: bool, +} + +impl PromotionRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + p_promo_sk: i64, + p_promo_id: String, + p_start_date_id: i64, + p_end_date_id: i64, + p_item_sk: i64, + p_cost: Decimal, + p_response_target: i32, + p_promo_name: String, + p_channel_dmail: bool, + p_channel_email: bool, + p_channel_catalog: bool, + p_channel_tv: bool, + p_channel_radio: bool, + p_channel_press: bool, + p_channel_event: bool, + p_channel_demo: bool, + p_channel_details: String, + p_purpose: String, + p_discount_active: bool, + ) -> Self { + PromotionRow { + null_bit_map, + p_promo_sk, + p_promo_id, + p_start_date_id, + p_end_date_id, + p_item_sk, + p_cost, + p_response_target, + p_promo_name, + p_channel_dmail, + p_channel_email, + p_channel_catalog, + p_channel_tv, + p_channel_radio, + p_channel_press, + p_channel_event, + p_channel_demo, + p_channel_details, + p_purpose, + p_discount_active, + } + } + + fn get_string_or_null_for_key(&self, key: i64, column: PromotionGeneratorColumn) -> String { + if key == -1 || self.is_null_at(column) { + String::new() + } else { + key.to_string() + } + } + + fn get_string_or_null_string(&self, value: &str, column: PromotionGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_decimal( + &self, + value: &Decimal, + column: PromotionGeneratorColumn, + ) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_int(&self, value: i32, column: PromotionGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_for_boolean( + &self, + value: bool, + column: PromotionGeneratorColumn, + ) -> String { + if self.is_null_at(column) { + String::new() + } else if value { + "Y".to_string() + } else { + "N".to_string() + } + } + + fn is_null_at(&self, column: PromotionGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - PromotionGeneratorColumn::PPromoSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } +} + +impl TableRow for PromotionRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null_for_key(self.p_promo_sk, PromotionGeneratorColumn::PPromoSk), + self.get_string_or_null_string(&self.p_promo_id, PromotionGeneratorColumn::PPromoId), + self.get_string_or_null_for_key( + self.p_start_date_id, + PromotionGeneratorColumn::PStartDateId, + ), + self.get_string_or_null_for_key( + self.p_end_date_id, + PromotionGeneratorColumn::PEndDateId, + ), + self.get_string_or_null_for_key(self.p_item_sk, PromotionGeneratorColumn::PItemSk), + self.get_string_or_null_decimal(&self.p_cost, PromotionGeneratorColumn::PCost), + self.get_string_or_null_int( + self.p_response_target, + PromotionGeneratorColumn::PResponseTarget, + ), + self.get_string_or_null_string( + &self.p_promo_name, + PromotionGeneratorColumn::PPromoName, + ), + self.get_string_or_null_for_boolean( + self.p_channel_dmail, + PromotionGeneratorColumn::PChannelDmail, + ), + self.get_string_or_null_for_boolean( + self.p_channel_email, + PromotionGeneratorColumn::PChannelEmail, + ), + self.get_string_or_null_for_boolean( + self.p_channel_catalog, + PromotionGeneratorColumn::PChannelCatalog, + ), + self.get_string_or_null_for_boolean( + self.p_channel_tv, + PromotionGeneratorColumn::PChannelTv, + ), + self.get_string_or_null_for_boolean( + self.p_channel_radio, + PromotionGeneratorColumn::PChannelRadio, + ), + self.get_string_or_null_for_boolean( + self.p_channel_press, + PromotionGeneratorColumn::PChannelPress, + ), + self.get_string_or_null_for_boolean( + self.p_channel_event, + PromotionGeneratorColumn::PChannelEvent, + ), + self.get_string_or_null_for_boolean( + self.p_channel_demo, + PromotionGeneratorColumn::PChannelDemo, + ), + self.get_string_or_null_string( + &self.p_channel_details, + PromotionGeneratorColumn::PChannelDetails, + ), + self.get_string_or_null_string(&self.p_purpose, PromotionGeneratorColumn::PPurpose), + self.get_string_or_null_for_boolean( + self.p_discount_active, + PromotionGeneratorColumn::PDiscountActive, + ), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_promotion_row_creation() { + let row = PromotionRow::new( + 0, + 1, + "test_id".to_string(), + 2450815, + 2450875, + 100, + Decimal::new(1000, 2).unwrap(), + 1, + "TestPromo".to_string(), + true, + false, + true, + false, + true, + false, + true, + false, + "Details".to_string(), + "Unknown".to_string(), + true, + ); + + assert_eq!(row.p_promo_sk, 1); + assert_eq!(row.p_promo_id, "test_id"); + } + + #[test] + fn test_promotion_row_values() { + let row = PromotionRow::new( + 0, + 1, + "AAAAAAAABAAAAAAA".to_string(), + 2450815, + 2450875, + 100, + Decimal::new(100000, 2).unwrap(), + 1, + "TestPromo".to_string(), + true, + false, + true, + false, + true, + false, + true, + false, + "Channel details".to_string(), + "Unknown".to_string(), + true, + ); + + let values = row.get_values(); + assert_eq!(values.len(), 19); + assert_eq!(values[0], "1"); + assert_eq!(values[1], "AAAAAAAABAAAAAAA"); + assert_eq!(values[8], "Y"); // p_channel_dmail + assert_eq!(values[9], "N"); // p_channel_email + } +} diff --git a/tpcdsgen/src/row/promotion_row_generator.rs b/tpcdsgen/src/row/promotion_row_generator.rs new file mode 100644 index 00000000..db5ee0f1 --- /dev/null +++ b/tpcdsgen/src/row/promotion_row_generator.rs @@ -0,0 +1,207 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::business_key_generator::make_business_key; +use crate::config::Table as ConfigTable; +use crate::generator::PromotionGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, PromotionRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::{Date, Decimal}; + +const PROMO_START_MIN: i32 = -720; +const PROMO_START_MAX: i32 = 100; +const PROMO_LENGTH_MIN: i32 = 1; +const PROMO_LENGTH_MAX: i32 = 60; +const PROMO_NAME_LENGTH: i32 = 5; +const PROMO_DETAIL_LENGTH_MIN: i32 = 20; +const PROMO_DETAIL_LENGTH_MAX: i32 = 60; + +pub struct PromotionRowGenerator { + abstract_row_generator: AbstractRowGenerator, +} + +impl PromotionRowGenerator { + pub fn new() -> Self { + PromotionRowGenerator { + abstract_row_generator: AbstractRowGenerator::new(Table::Promotion), + } + } +} + +impl Default for PromotionRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for PromotionRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &crate::config::Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> crate::error::Result { + let scaling = session.get_scaling(); + + let null_bit_map = create_null_bit_map( + Table::Promotion, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PNulls), + ); + + let p_promo_sk = row_number; + let p_promo_id = make_business_key(row_number); + + let p_start_date_id = Date::JULIAN_DATE_MINIMUM as i64 + + RandomValueGenerator::generate_uniform_random_int( + PROMO_START_MIN, + PROMO_START_MAX, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PStartDateId), + ) as i64; + + let p_end_date_id = p_start_date_id + + RandomValueGenerator::generate_uniform_random_int( + PROMO_LENGTH_MIN, + PROMO_LENGTH_MAX, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PEndDateId), + ) as i64; + + let p_item_sk = generate_join_key( + &PromotionGeneratorColumn::PItemSk, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PItemSk), + ConfigTable::Item, + 1, + scaling, + )?; + + let p_cost = Decimal::new(100000, 2)?; + let p_response_target = 1; + + // generate_word doesn't use random numbers - it deterministically creates from seed + // We still need to consume the stream to maintain RNG state + let _promo_name_stream = self + .abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PPromoName); + let p_promo_name = RandomValueGenerator::generate_word( + row_number, + PROMO_NAME_LENGTH, + crate::distribution::get_syllables_distribution(), + ); + + // Generate channel flags using a single random int (0-511) + let mut flags = RandomValueGenerator::generate_uniform_random_int( + 0, + 511, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PChannelDmail), + ); + + let p_channel_dmail = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_email = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_catalog = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_tv = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_radio = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_press = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_event = (flags & 0x01) != 0; + flags <<= 1; + + let p_channel_demo = (flags & 0x01) != 0; + flags <<= 1; + + let p_discount_active = (flags & 0x01) != 0; + + let p_channel_details = RandomValueGenerator::generate_random_text( + PROMO_DETAIL_LENGTH_MIN, + PROMO_DETAIL_LENGTH_MAX, + self.abstract_row_generator + .get_random_number_stream(&PromotionGeneratorColumn::PChannelDetails), + ); + + let p_purpose = "Unknown".to_string(); + + Ok(RowGeneratorResult::new(PromotionRow::new( + null_bit_map, + p_promo_sk, + p_promo_id, + p_start_date_id, + p_end_date_id, + p_item_sk, + p_cost, + p_response_target, + p_promo_name, + p_channel_dmail, + p_channel_email, + p_channel_catalog, + p_channel_tv, + p_channel_radio, + p_channel_press, + p_channel_event, + p_channel_demo, + p_channel_details, + p_purpose, + p_discount_active, + ))) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_row_generator + .consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_row_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} + +#[cfg(test)] +mod tests { + use crate::row::table_row::TableRow; + + use super::*; + + #[test] + fn test_generate_promotion_row() { + use crate::config::Session; + + let mut generator = PromotionRowGenerator::new(); + let session = Session::get_default_session(); + + let result = generator.generate_row_and_child_rows(1, &session, None, None); + assert!(result.is_ok()); + + let row_result = result.unwrap(); + let values = row_result.get_rows()[0].get_values(); + assert_eq!(values.len(), 19); + } +} diff --git a/tpcdsgen/src/row/reason_row.rs b/tpcdsgen/src/row/reason_row.rs new file mode 100644 index 00000000..6895eb3b --- /dev/null +++ b/tpcdsgen/src/row/reason_row.rs @@ -0,0 +1,73 @@ +use crate::row::TableRow; + +/// Reason table row (ReasonRow) +#[derive(Debug, Clone)] +pub struct ReasonRow { + null_bit_map: i64, + r_reason_sk: i64, + r_reason_id: String, + r_reason_description: String, +} + +impl ReasonRow { + pub fn new( + null_bit_map: i64, + r_reason_sk: i64, + r_reason_id: String, + r_reason_description: String, + ) -> Self { + ReasonRow { + null_bit_map, + r_reason_sk, + r_reason_id, + r_reason_description, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + /// Convert key to string or empty string if null (getStringOrNullForKey) + fn get_string_or_null_for_key(&self, value: i64, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_r_reason_sk(&self) -> i64 { + self.r_reason_sk + } + + pub fn get_r_reason_id(&self) -> &str { + &self.r_reason_id + } + + pub fn get_r_reason_description(&self) -> &str { + &self.r_reason_description + } +} + +impl TableRow for ReasonRow { + fn get_values(&self) -> Vec { + // Column positions match Java ReasonGeneratorColumn + // First column (R_REASON_SK) is at global position 248, so relative positions are 0-2 + vec![ + self.get_string_or_null_for_key(self.r_reason_sk, 0), + self.get_string_or_null(&self.r_reason_id, 1), + self.get_string_or_null(&self.r_reason_description, 2), + ] + } +} diff --git a/tpcdsgen/src/row/reason_row_generator.rs b/tpcdsgen/src/row/reason_row_generator.rs new file mode 100644 index 00000000..fad6d9e4 --- /dev/null +++ b/tpcdsgen/src/row/reason_row_generator.rs @@ -0,0 +1,79 @@ +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::ReturnReasonsDistribution; +use crate::error::Result; +use crate::generator::ReasonGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, ReasonRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; + +/// Row generator for the REASON table (ReasonRowGenerator) +pub struct ReasonRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for ReasonRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl ReasonRowGenerator { + /// Create a new ReasonRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::Reason), + } + } + + /// Generate a ReasonRow with realistic data following Java implementation + fn generate_reason_row(&mut self, row_number: i64, _session: &Session) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&ReasonGeneratorColumn::RNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = RandomValueGenerator::generate_uniform_random_int(1, i32::MAX, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::Reason.get_null_basis_points() { + (bit_map as i64) & !Table::Reason.get_not_null_bit_map() + } else { + 0 + }; + + let r_reason_sk = row_number; + let r_reason_id = make_business_key(row_number); + let r_reason_description = + ReturnReasonsDistribution::get_return_reason_at_index((row_number - 1) as usize)?; + + Ok(ReasonRow::new( + null_bit_map, + r_reason_sk, + r_reason_id.to_string(), + r_reason_description.to_string(), + )) + } +} + +impl RowGenerator for ReasonRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_reason_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/row_generator.rs b/tpcdsgen/src/row/row_generator.rs new file mode 100644 index 00000000..14d6f088 --- /dev/null +++ b/tpcdsgen/src/row/row_generator.rs @@ -0,0 +1,84 @@ +use crate::config::Session; +use crate::row::GeneratedRow; + +/// Result of row generation (RowGeneratorResult) +/// +/// Uses `GeneratedRow` enum instead of `Box` to avoid +/// heap allocations and enable static dispatch. See ISSUE-004. +pub struct RowGeneratorResult { + rows: Vec, + should_end_row: bool, +} + +impl RowGeneratorResult { + /// Create a result with a single row + pub fn new>(row: R) -> Self { + Self { + rows: vec![row.into()], + should_end_row: true, + } + } + + /// Create a result with multiple rows and end flag + pub fn new_with_multiple(rows: Vec, should_end_row: bool) -> Self { + Self { + rows, + should_end_row, + } + } + + /// Get the generated rows + pub fn get_rows(&self) -> &[GeneratedRow] { + &self.rows + } + + /// Check if row generation should end + pub fn should_end_row(&self) -> bool { + self.should_end_row + } +} + +/// RowGenerator trait matching the Java RowGenerator interface +pub trait RowGenerator: Send + Sync { + /// Generate a row and its child rows (generateRowAndChildRows) + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + parent_row_generator: Option<&mut dyn RowGenerator>, + child_row_generator: Option<&mut dyn RowGenerator>, + ) -> crate::error::Result; + + /// Consume remaining seeds for the current row + fn consume_remaining_seeds_for_row(&mut self); + + /// Skip rows until reaching the starting row number + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::row::CallCenterRow; + + #[test] + fn test_row_generator_result_single() { + let row = CallCenterRow::builder().build(); + let result = RowGeneratorResult::new(row); + + assert_eq!(result.get_rows().len(), 1); + assert!(result.should_end_row()); + } + + #[test] + fn test_row_generator_result_multiple() { + let rows = vec![ + GeneratedRow::from(CallCenterRow::builder().build()), + GeneratedRow::from(CallCenterRow::builder().build()), + ]; + let result = RowGeneratorResult::new_with_multiple(rows, false); + + assert_eq!(result.get_rows().len(), 2); + assert!(!result.should_end_row()); + } +} diff --git a/tpcdsgen/src/row/ship_mode_row.rs b/tpcdsgen/src/row/ship_mode_row.rs new file mode 100644 index 00000000..00e77c67 --- /dev/null +++ b/tpcdsgen/src/row/ship_mode_row.rs @@ -0,0 +1,97 @@ +use crate::row::TableRow; + +/// Ship mode table row (ShipModeRow) +#[derive(Debug, Clone)] +pub struct ShipModeRow { + null_bit_map: i64, + sm_ship_mode_sk: i64, + sm_ship_mode_id: String, + sm_type: String, + sm_code: String, + sm_carrier: String, + sm_contract: String, +} + +impl ShipModeRow { + pub fn new( + null_bit_map: i64, + sm_ship_mode_sk: i64, + sm_ship_mode_id: String, + sm_type: String, + sm_code: String, + sm_carrier: String, + sm_contract: String, + ) -> Self { + ShipModeRow { + null_bit_map, + sm_ship_mode_sk, + sm_ship_mode_id, + sm_type, + sm_code, + sm_carrier, + sm_contract, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + /// Convert key to string or empty string if null (getStringOrNullForKey) + fn get_string_or_null_for_key(&self, value: i64, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_sm_ship_mode_sk(&self) -> i64 { + self.sm_ship_mode_sk + } + + pub fn get_sm_ship_mode_id(&self) -> &str { + &self.sm_ship_mode_id + } + + pub fn get_sm_type(&self) -> &str { + &self.sm_type + } + + pub fn get_sm_code(&self) -> &str { + &self.sm_code + } + + pub fn get_sm_carrier(&self) -> &str { + &self.sm_carrier + } + + pub fn get_sm_contract(&self) -> &str { + &self.sm_contract + } +} + +impl TableRow for ShipModeRow { + fn get_values(&self) -> Vec { + // Column positions match Java ShipModeGeneratorColumn + // First column (SM_SHIP_MODE_SK) is at global position 252, so relative positions are 0-5 + vec![ + self.get_string_or_null_for_key(self.sm_ship_mode_sk, 0), + self.get_string_or_null(&self.sm_ship_mode_id, 1), + self.get_string_or_null(&self.sm_type, 2), + self.get_string_or_null(&self.sm_code, 3), + self.get_string_or_null(&self.sm_carrier, 4), + self.get_string_or_null(&self.sm_contract, 5), + ] + } +} diff --git a/tpcdsgen/src/row/ship_mode_row_generator.rs b/tpcdsgen/src/row/ship_mode_row_generator.rs new file mode 100644 index 00000000..bc424712 --- /dev/null +++ b/tpcdsgen/src/row/ship_mode_row_generator.rs @@ -0,0 +1,105 @@ +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::ShipModeDistributions; +use crate::error::Result; +use crate::generator::ShipModeGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult, ShipModeRow}; +use crate::table::Table; + +/// Row generator for the SHIP_MODE table (ShipModeRowGenerator) +pub struct ShipModeRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for ShipModeRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl ShipModeRowGenerator { + /// Create a new ShipModeRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::ShipMode), + } + } + + /// Generate a ShipModeRow with realistic data following Java implementation + fn generate_ship_mode_row( + &mut self, + row_number: i64, + _session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&ShipModeGeneratorColumn::SmNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = RandomValueGenerator::generate_uniform_random_int(1, i32::MAX, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::ShipMode.get_null_basis_points() { + (bit_map as i64) & !Table::ShipMode.get_not_null_bit_map() + } else { + 0 + }; + + let sm_ship_mode_sk = row_number; + let sm_ship_mode_id = make_business_key(row_number); + + let sm_type = ShipModeDistributions::get_ship_mode_type_for_index_mod_size(row_number)?; + + // Calculate index for code (divide by type distribution size) + let type_distribution_size = ShipModeDistributions::get_ship_mode_type_size() as i64; + let index = row_number / type_distribution_size; + + let sm_code = ShipModeDistributions::get_ship_mode_code_for_index_mod_size(index)?; + + let sm_carrier = + ShipModeDistributions::get_ship_mode_carrier_at_index((row_number - 1) as usize)?; + + let contract_stream = self + .abstract_generator + .get_random_number_stream(&ShipModeGeneratorColumn::SmContract); + let sm_contract = RandomValueGenerator::generate_random_charset( + RandomValueGenerator::ALPHA_NUMERIC, + 1, + 20, + contract_stream, + ); + + Ok(ShipModeRow::new( + null_bit_map, + sm_ship_mode_sk, + sm_ship_mode_id.to_string(), + sm_type.to_string(), + sm_code.to_string(), + sm_carrier.to_string(), + sm_contract, + )) + } +} + +impl RowGenerator for ShipModeRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_ship_mode_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/store_returns_row.rs b/tpcdsgen/src/row/store_returns_row.rs new file mode 100644 index 00000000..276a0c3a --- /dev/null +++ b/tpcdsgen/src/row/store_returns_row.rs @@ -0,0 +1,219 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store returns row data structure + +use crate::generator::{GeneratorColumn, StoreReturnsGeneratorColumn}; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row data structure for the store_returns table +#[derive(Debug, Clone)] +pub struct StoreReturnsRow { + null_bit_map: i64, + sr_returned_date_sk: i64, + sr_returned_time_sk: i64, + sr_item_sk: i64, + sr_customer_sk: i64, + sr_cdemo_sk: i64, + sr_hdemo_sk: i64, + sr_addr_sk: i64, + sr_store_sk: i64, + sr_reason_sk: i64, + sr_ticket_number: i64, + sr_pricing: Pricing, +} + +impl StoreReturnsRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + sr_returned_date_sk: i64, + sr_returned_time_sk: i64, + sr_item_sk: i64, + sr_customer_sk: i64, + sr_cdemo_sk: i64, + sr_hdemo_sk: i64, + sr_addr_sk: i64, + sr_store_sk: i64, + sr_reason_sk: i64, + sr_ticket_number: i64, + sr_pricing: Pricing, + ) -> Self { + StoreReturnsRow { + null_bit_map, + sr_returned_date_sk, + sr_returned_time_sk, + sr_item_sk, + sr_customer_sk, + sr_cdemo_sk, + sr_hdemo_sk, + sr_addr_sk, + sr_store_sk, + sr_reason_sk, + sr_ticket_number, + sr_pricing, + } + } + + fn get_string_or_null_for_key(&self, key: i64, column: StoreReturnsGeneratorColumn) -> String { + if key == -1 || self.is_null_at(column) { + String::new() + } else { + key.to_string() + } + } + + fn get_string_or_null_int(&self, value: i32, column: StoreReturnsGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_decimal( + &self, + value: &crate::types::Decimal, + column: StoreReturnsGeneratorColumn, + ) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn is_null_at(&self, column: StoreReturnsGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - StoreReturnsGeneratorColumn::SrReturnedDateSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } +} + +impl TableRow for StoreReturnsRow { + fn get_values(&self) -> Vec { + use StoreReturnsGeneratorColumn::*; + + vec![ + self.get_string_or_null_for_key(self.sr_returned_date_sk, SrReturnedDateSk), + self.get_string_or_null_for_key(self.sr_returned_time_sk, SrReturnedTimeSk), + self.get_string_or_null_for_key(self.sr_item_sk, SrItemSk), + self.get_string_or_null_for_key(self.sr_customer_sk, SrCustomerSk), + self.get_string_or_null_for_key(self.sr_cdemo_sk, SrCdemoSk), + self.get_string_or_null_for_key(self.sr_hdemo_sk, SrHdemoSk), + self.get_string_or_null_for_key(self.sr_addr_sk, SrAddrSk), + self.get_string_or_null_for_key(self.sr_store_sk, SrStoreSk), + self.get_string_or_null_for_key(self.sr_reason_sk, SrReasonSk), + self.get_string_or_null_for_key(self.sr_ticket_number, SrTicketNumber), + self.get_string_or_null_int(self.sr_pricing.get_quantity(), SrPricingQuantity), + self.get_string_or_null_decimal(&self.sr_pricing.get_net_paid(), SrPricingNetPaid), + self.get_string_or_null_decimal(&self.sr_pricing.get_ext_tax(), SrPricingExtTax), + self.get_string_or_null_decimal( + &self.sr_pricing.get_net_paid_including_tax(), + SrPricingNetPaidIncTax, + ), + self.get_string_or_null_decimal(&self.sr_pricing.get_fee(), SrPricingFee), + self.get_string_or_null_decimal( + &self.sr_pricing.get_ext_ship_cost(), + SrPricingExtShipCost, + ), + self.get_string_or_null_decimal( + &self.sr_pricing.get_refunded_cash(), + SrPricingRefundedCash, + ), + self.get_string_or_null_decimal( + &self.sr_pricing.get_reversed_charge(), + SrPricingReversedCharge, + ), + self.get_string_or_null_decimal( + &self.sr_pricing.get_store_credit(), + SrPricingStoreCredit, + ), + self.get_string_or_null_decimal(&self.sr_pricing.get_net_loss(), SrPricingNetLoss), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::Decimal; + + fn create_test_pricing() -> Pricing { + Pricing::new( + Decimal::new(1000, 2).unwrap(), // wholesale_cost: 10.00 + Decimal::new(1500, 2).unwrap(), // list_price: 15.00 + Decimal::new(1200, 2).unwrap(), // sales_price: 12.00 + 5, // quantity + Decimal::new(300, 2).unwrap(), // ext_discount_amount: 3.00 + Decimal::new(6000, 2).unwrap(), // ext_sales_price: 60.00 + Decimal::new(5000, 2).unwrap(), // ext_wholesale_cost: 50.00 + Decimal::new(7500, 2).unwrap(), // ext_list_price: 75.00 + Decimal::new(8, 2).unwrap(), // tax_percent: 0.08 + Decimal::new(480, 2).unwrap(), // ext_tax: 4.80 + Decimal::new(100, 2).unwrap(), // coupon_amount: 1.00 + Decimal::new(200, 2).unwrap(), // ship_cost: 2.00 + Decimal::new(1000, 2).unwrap(), // ext_ship_cost: 10.00 + Decimal::new(5900, 2).unwrap(), // net_paid: 59.00 + Decimal::new(6380, 2).unwrap(), // net_paid_including_tax: 63.80 + Decimal::new(6900, 2).unwrap(), // net_paid_including_shipping: 69.00 + Decimal::new(7380, 2).unwrap(), // net_paid_including_shipping_and_tax: 73.80 + Decimal::new(900, 2).unwrap(), // net_profit: 9.00 + Decimal::new(2000, 2).unwrap(), // refunded_cash: 20.00 + Decimal::new(1000, 2).unwrap(), // reversed_charge: 10.00 + Decimal::new(2900, 2).unwrap(), // store_credit: 29.00 + Decimal::new(500, 2).unwrap(), // fee: 5.00 + Decimal::new(1580, 2).unwrap(), // net_loss: 15.80 + ) + } + + #[test] + fn test_store_returns_row_creation() { + let pricing = create_test_pricing(); + let row = StoreReturnsRow::new( + 0, // null_bit_map + 2451545, // sr_returned_date_sk + 36000, // sr_returned_time_sk + 1, // sr_item_sk + 100, // sr_customer_sk + 200, // sr_cdemo_sk + 300, // sr_hdemo_sk + 400, // sr_addr_sk + 500, // sr_store_sk + 600, // sr_reason_sk + 1, // sr_ticket_number + pricing, + ); + + let values = row.get_values(); + assert_eq!(values.len(), 20); + assert_eq!(values[0], "2451545"); // sr_returned_date_sk + assert_eq!(values[9], "1"); // sr_ticket_number + } + + #[test] + fn test_store_returns_row_null_handling() { + let pricing = create_test_pricing(); + // Set bit for sr_returned_time_sk (position 1) + let row = StoreReturnsRow::new( + 0b10, // null_bit_map - second bit set + 2451545, 36000, 1, 100, 200, 300, 400, 500, 600, 1, pricing, + ); + + let values = row.get_values(); + assert_eq!(values[0], "2451545"); // not null + assert_eq!(values[1], ""); // null (bit 1 set) + } +} diff --git a/tpcdsgen/src/row/store_returns_row_generator.rs b/tpcdsgen/src/row/store_returns_row_generator.rs new file mode 100644 index 00000000..ce576d8f --- /dev/null +++ b/tpcdsgen/src/row/store_returns_row_generator.rs @@ -0,0 +1,218 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store returns row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::StoreReturnsGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::store_returns_row::StoreReturnsRow; +use crate::row::store_sales_row::StoreSalesRow; +use crate::row::{AbstractRowGenerator, GeneratedRow, RowGenerator, RowGeneratorResult}; +use crate::table::Table; +use crate::types::generate_pricing_for_returns_table; + +/// Percentage of returns where the same customer returns the item +const SR_SAME_CUSTOMER: i32 = 80; + +pub struct StoreReturnsRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl StoreReturnsRowGenerator { + pub fn new() -> Self { + StoreReturnsRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::StoreReturns), + } + } + + /// Generate a return row from a sales row + /// This is called by StoreSalesRowGenerator when a sale is returned + pub fn generate_row( + &mut self, + session: &Session, + sales_row: &StoreSalesRow, + ) -> Result { + use StoreReturnsGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&SrNulls); + let null_bit_map = create_null_bit_map(Table::StoreReturns, stream); + + // Some of the information in the return is taken from the original sale + let sr_ticket_number = sales_row.get_ss_ticket_number(); + let sr_item_sk = sales_row.get_ss_sold_item_sk(); + + // Some fields are conditionally taken from the sale (80% same customer) + let stream = self + .abstract_generator + .get_random_number_stream(&SrCustomerSk); + let mut sr_customer_sk = generate_join_key( + &SrCustomerSk, + stream, + crate::config::Table::Customer, + 1, + scaling, + )?; + let stream = self + .abstract_generator + .get_random_number_stream(&SrTicketNumber); + let random_int = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + if random_int < SR_SAME_CUSTOMER { + sr_customer_sk = sales_row.get_ss_sold_customer_sk(); + } + + // The rest of the columns are generated for this specific return + let stream = self + .abstract_generator + .get_random_number_stream(&SrReturnedDateSk); + let sr_returned_date_sk = generate_join_key( + &SrReturnedDateSk, + stream, + crate::config::Table::DateDim, + sales_row.get_ss_sold_date_sk(), + scaling, + )?; + + // Return time is between 8am and 5pm (8*3600-1 to 17*3600-1 seconds) + let stream = self + .abstract_generator + .get_random_number_stream(&SrReturnedTimeSk); + let sr_returned_time_sk = + RandomValueGenerator::generate_uniform_random_int(8 * 3600 - 1, 17 * 3600 - 1, stream) + as i64; + + let stream = self.abstract_generator.get_random_number_stream(&SrCdemoSk); + let sr_cdemo_sk = generate_join_key( + &SrCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self.abstract_generator.get_random_number_stream(&SrHdemoSk); + let sr_hdemo_sk = generate_join_key( + &SrHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self.abstract_generator.get_random_number_stream(&SrAddrSk); + let sr_addr_sk = generate_join_key( + &SrAddrSk, + stream, + crate::config::Table::CustomerAddress, + 1, + scaling, + )?; + + let stream = self.abstract_generator.get_random_number_stream(&SrStoreSk); + let sr_store_sk = + generate_join_key(&SrStoreSk, stream, crate::config::Table::Store, 1, scaling)?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SrReasonSk); + let sr_reason_sk = generate_join_key( + &SrReasonSk, + stream, + crate::config::Table::Reason, + 1, + scaling, + )?; + + // Generate return quantity (1 to original sale quantity) + let sales_pricing = sales_row.get_ss_pricing(); + let stream = self.abstract_generator.get_random_number_stream(&SrPricing); + let quantity = RandomValueGenerator::generate_uniform_random_int( + 1, + sales_pricing.get_quantity(), + stream, + ); + + // Generate return pricing + let stream = self.abstract_generator.get_random_number_stream(&SrPricing); + let sr_pricing = generate_pricing_for_returns_table(stream, quantity, sales_pricing); + + Ok(StoreReturnsRow::new( + null_bit_map, + sr_returned_date_sk, + sr_returned_time_sk, + sr_item_sk, + sr_customer_sk, + sr_cdemo_sk, + sr_hdemo_sk, + sr_addr_sk, + sr_store_sk, + sr_reason_sk, + sr_ticket_number, + sr_pricing, + ) + .into()) + } +} + +impl Default for StoreReturnsRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for StoreReturnsRowGenerator { + fn generate_row_and_child_rows( + &mut self, + _row_number: i64, + _session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + // The store_returns table is a child of the store_sales table because you can only + // return things that have already been purchased. This method should only get called + // if we are generating the store_returns table in isolation. + // Otherwise store_returns is generated during the generation of the store_sales table + // via the generate_row method above. + // + // For now, we panic if called directly - the proper way is to generate through + // store_sales which calls our generate_row method. + panic!("StoreReturnsRowGenerator::generate_row_and_child_rows should not be called directly. Use StoreSalesRowGenerator to generate both sales and returns."); + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_store_returns_row_generator_creation() { + let _generator = StoreReturnsRowGenerator::new(); + // Just test that it creates successfully + } +} diff --git a/tpcdsgen/src/row/store_row.rs b/tpcdsgen/src/row/store_row.rs new file mode 100644 index 00000000..138ad7df --- /dev/null +++ b/tpcdsgen/src/row/store_row.rs @@ -0,0 +1,230 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store row structure and formatting + +use crate::generator::{GeneratorColumn, StoreGeneratorColumn}; +use crate::row::TableRow; +use crate::types::{Address, Date, Decimal}; + +/// Store row +#[derive(Clone)] +pub struct StoreRow { + null_bit_map: i64, + store_sk: i64, + store_id: String, + rec_start_date_id: i64, + rec_end_date_id: i64, + closed_date_id: i64, + store_name: String, + employees: i32, + floor_space: i32, + hours: String, + store_manager: String, + market_id: i32, + d_tax_percentage: Decimal, + geography_class: String, + market_desc: String, + market_manager: String, + division_id: i64, + division_name: String, + company_id: i64, + company_name: String, + address: Address, +} + +impl StoreRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + store_sk: i64, + store_id: String, + rec_start_date_id: i64, + rec_end_date_id: i64, + closed_date_id: i64, + store_name: String, + employees: i32, + floor_space: i32, + hours: String, + store_manager: String, + market_id: i32, + d_tax_percentage: Decimal, + geography_class: String, + market_desc: String, + market_manager: String, + division_id: i64, + division_name: String, + company_id: i64, + company_name: String, + address: Address, + ) -> Self { + StoreRow { + null_bit_map, + store_sk, + store_id, + rec_start_date_id, + rec_end_date_id, + closed_date_id, + store_name, + employees, + floor_space, + hours, + store_manager, + market_id, + d_tax_percentage, + geography_class, + market_desc, + market_manager, + division_id, + division_name, + company_id, + company_name, + address, + } + } + + fn is_null(&self, column: &StoreGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - StoreGeneratorColumn::WStoreSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key(&self, value: i64, column: &StoreGeneratorColumn) -> String { + if self.is_null(column) || value < 0 { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null(&self, value: &str, column: &StoreGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_int_or_null(&self, value: i32, column: &StoreGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_decimal_or_null(&self, value: &Decimal, column: &StoreGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_date_string_or_null(&self, julian_days: i64, column: &StoreGeneratorColumn) -> String { + if self.is_null(column) || julian_days < 0 { + String::new() + } else { + Date::from_julian_days(julian_days as i32).to_string() + } + } + + // Getters for SCD fields + pub fn get_closed_date_id(&self) -> i64 { + self.closed_date_id + } + + pub fn get_store_name(&self) -> &str { + &self.store_name + } + + pub fn get_employees(&self) -> i32 { + self.employees + } + + pub fn get_floor_space(&self) -> i32 { + self.floor_space + } + + pub fn get_hours(&self) -> &str { + &self.hours + } + + pub fn get_store_manager(&self) -> &str { + &self.store_manager + } + + pub fn get_market_id(&self) -> i32 { + self.market_id + } + + pub fn get_d_tax_percentage(&self) -> Decimal { + self.d_tax_percentage + } + + pub fn get_market_desc(&self) -> &str { + &self.market_desc + } + + pub fn get_market_manager(&self) -> &str { + &self.market_manager + } + + pub fn get_address(&self) -> &Address { + &self.address + } +} + +impl TableRow for StoreRow { + fn get_values(&self) -> Vec { + use StoreGeneratorColumn::*; + vec![ + self.get_string_or_null_for_key(self.store_sk, &WStoreSk), + self.get_string_or_null(&self.store_id, &WStoreId), + self.get_date_string_or_null(self.rec_start_date_id, &WStoreRecStartDateId), + self.get_date_string_or_null(self.rec_end_date_id, &WStoreRecEndDateId), + self.get_string_or_null_for_key(self.closed_date_id, &WStoreClosedDateId), + self.get_string_or_null(&self.store_name, &WStoreName), + self.get_int_or_null(self.employees, &WStoreEmployees), + self.get_int_or_null(self.floor_space, &WStoreFloorSpace), + self.get_string_or_null(&self.hours, &WStoreHours), + self.get_string_or_null(&self.store_manager, &WStoreManager), + self.get_int_or_null(self.market_id, &WStoreMarketId), + self.get_string_or_null(&self.geography_class, &WStoreGeographyClass), + self.get_string_or_null(&self.market_desc, &WStoreMarketDesc), + self.get_string_or_null(&self.market_manager, &WStoreMarketManager), + self.get_string_or_null_for_key(self.division_id, &WStoreDivisionId), + self.get_string_or_null(&self.division_name, &WStoreDivisionName), + self.get_string_or_null_for_key(self.company_id, &WStoreCompanyId), + self.get_string_or_null(&self.company_name, &WStoreCompanyName), + self.get_int_or_null(self.address.get_street_number(), &WStoreAddressStreetNum), + self.get_string_or_null(&self.address.get_street_name(), &WStoreAddressStreetName1), + self.get_string_or_null(self.address.get_street_type(), &WStoreAddressStreetType), + self.get_string_or_null(self.address.get_suite_number(), &WStoreAddressSuiteNum), + self.get_string_or_null(self.address.get_city(), &WStoreAddressCity), + self.get_string_or_null( + self.address.get_county().unwrap_or(""), + &WStoreAddressCounty, + ), + self.get_string_or_null(self.address.get_state(), &WStoreAddressState), + self.get_string_or_null(&format!("{:05}", self.address.get_zip()), &WStoreAddressZip), + self.get_string_or_null(self.address.get_country(), &WStoreAddressCountry), + self.get_string_or_null( + &self.address.get_gmt_offset().to_string(), + &WStoreAddressGmtOffset, + ), + self.get_decimal_or_null(&self.d_tax_percentage, &WStoreTaxPercentage), + ] + } +} diff --git a/tpcdsgen/src/row/store_row_generator.rs b/tpcdsgen/src/row/store_row_generator.rs new file mode 100644 index 00000000..67cf3df7 --- /dev/null +++ b/tpcdsgen/src/row/store_row_generator.rs @@ -0,0 +1,390 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store row generator (Slowly Changing Dimension) + +use crate::config::Session; +use crate::distribution::{CallCenterDistributions, FirstNamesWeights, NamesDistributions}; +use crate::error::Result; +use crate::generator::StoreGeneratorColumn; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::store_row::StoreRow; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::{ + compute_scd_key, get_value_for_slowly_changing_dimension, +}; +use crate::table::Table; +use crate::types::{Address, Date, Decimal}; + +const ROW_SIZE_S_MARKET_DESC: i32 = 100; +fn store_min_tax_percentage() -> Decimal { + Decimal::new(0, 2).unwrap() +} +fn store_max_tax_percentage() -> Decimal { + Decimal::new(11, 2).unwrap() +} +const STORE_MIN_DAYS_OPEN: i32 = 5; +const STORE_MAX_DAYS_OPEN: i32 = 500; +const STORE_CLOSED_PCT: i32 = 30; +const STORE_DESC_MIN: i32 = 15; + +pub struct StoreRowGenerator { + abstract_generator: AbstractRowGenerator, + previous_row: Option, +} + +impl StoreRowGenerator { + pub fn new() -> Self { + StoreRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::Store), + previous_row: None, + } + } + + fn generate_store_row(&mut self, row_number: i64, session: &Session) -> Result { + use StoreGeneratorColumn::*; + + // Generate null bit map first + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreNulls); + let null_bit_map = create_null_bit_map(Table::Store, stream); + + let store_sk = row_number; + + // Compute SCD key using S_STORE table + let scd_key = compute_scd_key(Table::SStore, row_number); + let store_id = scd_key.get_business_key().to_string(); + let rec_start_date_id = scd_key.get_start_date(); + let rec_end_date_id = scd_key.get_end_date(); + let is_new_business_key = scd_key.is_new_business_key(); + + // Get field change flags + let stream = self.abstract_generator.get_random_number_stream(&WStoreScd); + let mut field_change_flags = stream.next_random() as i32; + + // Generate closed date + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreClosedDateId); + let percentage = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreClosedDateId); + let days_open = RandomValueGenerator::generate_uniform_random_int( + STORE_MIN_DAYS_OPEN, + STORE_MAX_DAYS_OPEN, + stream, + ); + let mut closed_date_id: i64 = if percentage < STORE_CLOSED_PCT { + Date::JULIAN_DATE_MINIMUM as i64 + days_open as i64 + } else { + -1 + }; + if let Some(ref prev_row) = self.previous_row { + closed_date_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_closed_date_id(), + closed_date_id, + ); + } + field_change_flags >>= 1; + + // Generate store name + let mut store_name = RandomValueGenerator::generate_word( + row_number, + 5, + crate::distribution::get_syllables_distribution(), + ); + if let Some(ref prev_row) = self.previous_row { + store_name = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_store_name().to_string(), + store_name, + ); + } + field_change_flags >>= 1; + + // Generate employees + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreEmployees); + let mut employees = RandomValueGenerator::generate_uniform_random_int(200, 300, stream); + if let Some(ref prev_row) = self.previous_row { + employees = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_employees(), + employees, + ); + } + field_change_flags >>= 1; + + // Generate floor space + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreFloorSpace); + let mut floor_space = + RandomValueGenerator::generate_uniform_random_int(5000000, 10000000, stream); + if let Some(ref prev_row) = self.previous_row { + floor_space = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_floor_space(), + floor_space, + ); + } + field_change_flags >>= 1; + + // Generate hours + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreHours); + let hours = CallCenterDistributions::pick_random_call_center_hours(stream)?.to_string(); + field_change_flags >>= 1; + + // Generate store manager + let weights = if session.is_sexist() { + FirstNamesWeights::MaleFrequency + } else { + FirstNamesWeights::GeneralFrequency + }; + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreManager); + let first_name = NamesDistributions::pick_random_first_name(weights, stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreManager); + let last_name = NamesDistributions::pick_random_last_name(stream)?; + let mut store_manager = format!("{} {}", first_name, last_name); + if let Some(ref prev_row) = self.previous_row { + store_manager = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_store_manager().to_string(), + store_manager, + ); + } + field_change_flags >>= 1; + + // Generate market ID + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreMarketId); + let mut market_id = RandomValueGenerator::generate_uniform_random_int(1, 10, stream); + if let Some(ref prev_row) = self.previous_row { + market_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_market_id(), + market_id, + ); + } + field_change_flags >>= 1; + + // Generate tax percentage + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreTaxPercentage); + let mut d_tax_percentage = RandomValueGenerator::generate_uniform_random_decimal( + store_min_tax_percentage(), + store_max_tax_percentage(), + stream, + ); + if let Some(ref prev_row) = self.previous_row { + d_tax_percentage = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_d_tax_percentage(), + d_tax_percentage, + ); + } + field_change_flags >>= 1; + + // Geography class is always "Unknown" + let geography_class = "Unknown".to_string(); + field_change_flags >>= 1; + + // Generate market description + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreMarketDesc); + let mut market_desc = RandomValueGenerator::generate_random_text( + STORE_DESC_MIN, + ROW_SIZE_S_MARKET_DESC, + stream, + ); + if let Some(ref prev_row) = self.previous_row { + market_desc = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_market_desc().to_string(), + market_desc, + ); + } + field_change_flags >>= 1; + + // Generate market manager + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreMarketManager); + let first_name = NamesDistributions::pick_random_first_name(weights, stream)?; + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreMarketManager); + let last_name = NamesDistributions::pick_random_last_name(stream)?; + let mut market_manager = format!("{} {}", first_name, last_name); + if let Some(ref prev_row) = self.previous_row { + market_manager = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_market_manager().to_string(), + market_manager, + ); + } + field_change_flags >>= 1; + + // Division and company are constant "Unknown" and 1 + let division_name = "Unknown".to_string(); + let division_id: i64 = 1; + field_change_flags >>= 1; // divisionId + field_change_flags >>= 1; // divisionName + + let company_name = "Unknown".to_string(); + let company_id: i64 = 1; + field_change_flags >>= 1; // companyId + field_change_flags >>= 1; // companyName + + // Generate address - many fields don't get updated due to C bug + let stream = self + .abstract_generator + .get_random_number_stream(&WStoreAddress); + let mut address = + Address::make_address_for_column(Table::Store, stream, session.get_scaling())?; + field_change_flags >>= 1; // city + field_change_flags >>= 1; // county + + let mut gmt_offset = address.get_gmt_offset(); + if let Some(ref prev_row) = self.previous_row { + gmt_offset = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_address().get_gmt_offset(), + gmt_offset, + ); + } + field_change_flags >>= 1; + + field_change_flags >>= 1; // state + field_change_flags >>= 1; // streetType + field_change_flags >>= 1; // streetName1 + field_change_flags >>= 1; // streetName2 + + let mut street_number = address.get_street_number(); + if let Some(ref prev_row) = self.previous_row { + street_number = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_address().get_street_number(), + street_number, + ); + } + field_change_flags >>= 1; + + let mut zip = address.get_zip(); + if let Some(ref prev_row) = self.previous_row { + zip = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev_row.get_address().get_zip(), + zip, + ); + } + + // Create new address with updated SCD fields + address = Address::new( + address.get_suite_number().to_string(), + street_number, + address.get_street_name1().to_string(), + address.get_street_name2().to_string(), + address.get_street_type().to_string(), + address.get_city().to_string(), + address.get_county().map(|s| s.to_string()), + address.get_state().to_string(), + address.get_country().to_string(), + zip, + gmt_offset, + )?; + + let row = StoreRow::new( + null_bit_map, + store_sk, + store_id, + rec_start_date_id, + rec_end_date_id, + closed_date_id, + store_name, + employees, + floor_space, + hours, + store_manager, + market_id, + d_tax_percentage, + geography_class, + market_desc, + market_manager, + division_id, + division_name, + company_id, + company_name, + address, + ); + + Ok(row) + } +} + +impl Default for StoreRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for StoreRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_store_row(row_number, session)?; + // Store for SCD logic on next row + self.previous_row = Some(row.clone()); + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/store_sales_row.rs b/tpcdsgen/src/row/store_sales_row.rs new file mode 100644 index 00000000..d3dccfef --- /dev/null +++ b/tpcdsgen/src/row/store_sales_row.rs @@ -0,0 +1,273 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store sales row data structure + +use crate::generator::{GeneratorColumn, StoreSalesGeneratorColumn}; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row data structure for the store_sales table +#[derive(Debug, Clone)] +pub struct StoreSalesRow { + null_bit_map: i64, + ss_sold_date_sk: i64, + ss_sold_time_sk: i64, + ss_sold_item_sk: i64, + ss_sold_customer_sk: i64, + ss_sold_cdemo_sk: i64, + ss_sold_hdemo_sk: i64, + ss_sold_addr_sk: i64, + ss_sold_store_sk: i64, + ss_sold_promo_sk: i64, + ss_ticket_number: i64, + ss_pricing: Pricing, +} + +impl StoreSalesRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + ss_sold_date_sk: i64, + ss_sold_time_sk: i64, + ss_sold_item_sk: i64, + ss_sold_customer_sk: i64, + ss_sold_cdemo_sk: i64, + ss_sold_hdemo_sk: i64, + ss_sold_addr_sk: i64, + ss_sold_store_sk: i64, + ss_sold_promo_sk: i64, + ss_ticket_number: i64, + ss_pricing: Pricing, + ) -> Self { + StoreSalesRow { + null_bit_map, + ss_sold_date_sk, + ss_sold_time_sk, + ss_sold_item_sk, + ss_sold_customer_sk, + ss_sold_cdemo_sk, + ss_sold_hdemo_sk, + ss_sold_addr_sk, + ss_sold_store_sk, + ss_sold_promo_sk, + ss_ticket_number, + ss_pricing, + } + } + + /// Get the ticket number (for store_returns generation) + pub fn get_ss_ticket_number(&self) -> i64 { + self.ss_ticket_number + } + + /// Get the sold item sk (for store_returns generation) + pub fn get_ss_sold_item_sk(&self) -> i64 { + self.ss_sold_item_sk + } + + /// Get the sold customer sk (for store_returns generation) + pub fn get_ss_sold_customer_sk(&self) -> i64 { + self.ss_sold_customer_sk + } + + /// Get the sold date sk (for store_returns generation) + pub fn get_ss_sold_date_sk(&self) -> i64 { + self.ss_sold_date_sk + } + + /// Get the pricing (for store_returns generation) + pub fn get_ss_pricing(&self) -> &Pricing { + &self.ss_pricing + } + + fn get_string_or_null_for_key(&self, key: i64, column: StoreSalesGeneratorColumn) -> String { + if key == -1 || self.is_null_at(column) { + String::new() + } else { + key.to_string() + } + } + + fn get_string_or_null_int(&self, value: i32, column: StoreSalesGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_decimal( + &self, + value: &crate::types::Decimal, + column: StoreSalesGeneratorColumn, + ) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn is_null_at(&self, column: StoreSalesGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - StoreSalesGeneratorColumn::SsSoldDateSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } +} + +impl TableRow for StoreSalesRow { + fn get_values(&self) -> Vec { + use StoreSalesGeneratorColumn::*; + + // Note: Java has coupon_amount twice at positions 15 and 20 (bug in original) + // We replicate this for byte-for-byte compatibility + vec![ + self.get_string_or_null_for_key(self.ss_sold_date_sk, SsSoldDateSk), + self.get_string_or_null_for_key(self.ss_sold_time_sk, SsSoldTimeSk), + self.get_string_or_null_for_key(self.ss_sold_item_sk, SsSoldItemSk), + self.get_string_or_null_for_key(self.ss_sold_customer_sk, SsSoldCustomerSk), + self.get_string_or_null_for_key(self.ss_sold_cdemo_sk, SsSoldCdemoSk), + self.get_string_or_null_for_key(self.ss_sold_hdemo_sk, SsSoldHdemoSk), + self.get_string_or_null_for_key(self.ss_sold_addr_sk, SsSoldAddrSk), + self.get_string_or_null_for_key(self.ss_sold_store_sk, SsSoldStoreSk), + self.get_string_or_null_for_key(self.ss_sold_promo_sk, SsSoldPromoSk), + self.get_string_or_null_for_key(self.ss_ticket_number, SsTicketNumber), + self.get_string_or_null_int(self.ss_pricing.get_quantity(), SsPricingQuantity), + self.get_string_or_null_decimal( + &self.ss_pricing.get_wholesale_cost(), + SsPricingWholesaleCost, + ), + self.get_string_or_null_decimal(&self.ss_pricing.get_list_price(), SsPricingListPrice), + self.get_string_or_null_decimal( + &self.ss_pricing.get_sales_price(), + SsPricingSalesPrice, + ), + self.get_string_or_null_decimal( + &self.ss_pricing.get_coupon_amount(), + SsPricingCouponAmt, + ), + self.get_string_or_null_decimal( + &self.ss_pricing.get_ext_sales_price(), + SsPricingExtSalesPrice, + ), + self.get_string_or_null_decimal( + &self.ss_pricing.get_ext_wholesale_cost(), + SsPricingExtWholesaleCost, + ), + self.get_string_or_null_decimal( + &self.ss_pricing.get_ext_list_price(), + SsPricingExtListPrice, + ), + self.get_string_or_null_decimal(&self.ss_pricing.get_ext_tax(), SsPricingExtTax), + // Note: coupon_amount appears twice in Java (bug replicated for compatibility) + self.get_string_or_null_decimal( + &self.ss_pricing.get_coupon_amount(), + SsPricingCouponAmt, + ), + self.get_string_or_null_decimal(&self.ss_pricing.get_net_paid(), SsPricingNetPaid), + self.get_string_or_null_decimal( + &self.ss_pricing.get_net_paid_including_tax(), + SsPricingNetPaidIncTax, + ), + self.get_string_or_null_decimal(&self.ss_pricing.get_net_profit(), SsPricingNetProfit), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::Decimal; + + fn create_test_pricing() -> Pricing { + Pricing::new( + Decimal::new(1000, 2).unwrap(), // wholesale_cost: 10.00 + Decimal::new(1500, 2).unwrap(), // list_price: 15.00 + Decimal::new(1200, 2).unwrap(), // sales_price: 12.00 + 5, // quantity + Decimal::new(300, 2).unwrap(), // ext_discount_amount: 3.00 + Decimal::new(6000, 2).unwrap(), // ext_sales_price: 60.00 + Decimal::new(5000, 2).unwrap(), // ext_wholesale_cost: 50.00 + Decimal::new(7500, 2).unwrap(), // ext_list_price: 75.00 + Decimal::new(8, 2).unwrap(), // tax_percent: 0.08 + Decimal::new(480, 2).unwrap(), // ext_tax: 4.80 + Decimal::new(100, 2).unwrap(), // coupon_amount: 1.00 + Decimal::new(200, 2).unwrap(), // ship_cost: 2.00 + Decimal::new(1000, 2).unwrap(), // ext_ship_cost: 10.00 + Decimal::new(5900, 2).unwrap(), // net_paid: 59.00 + Decimal::new(6380, 2).unwrap(), // net_paid_including_tax: 63.80 + Decimal::new(6900, 2).unwrap(), // net_paid_including_shipping: 69.00 + Decimal::new(7380, 2).unwrap(), // net_paid_including_shipping_and_tax: 73.80 + Decimal::new(900, 2).unwrap(), // net_profit: 9.00 + Decimal::ZERO, // refunded_cash + Decimal::ZERO, // reversed_charge + Decimal::ZERO, // store_credit + Decimal::ZERO, // fee + Decimal::ZERO, // net_loss + ) + } + + #[test] + fn test_store_sales_row_creation() { + let pricing = create_test_pricing(); + let row = StoreSalesRow::new( + 0, // null_bit_map + 2451545, // ss_sold_date_sk + 36000, // ss_sold_time_sk + 1, // ss_sold_item_sk + 100, // ss_sold_customer_sk + 200, // ss_sold_cdemo_sk + 300, // ss_sold_hdemo_sk + 400, // ss_sold_addr_sk + 500, // ss_sold_store_sk + 600, // ss_sold_promo_sk + 1, // ss_ticket_number + pricing, + ); + + assert_eq!(row.get_ss_ticket_number(), 1); + assert_eq!(row.get_ss_sold_item_sk(), 1); + assert_eq!(row.get_ss_sold_customer_sk(), 100); + } + + #[test] + fn test_store_sales_row_values() { + let pricing = create_test_pricing(); + let row = StoreSalesRow::new( + 0, 2451545, 36000, 1, 100, 200, 300, 400, 500, 600, 1, pricing, + ); + + let values = row.get_values(); + assert_eq!(values.len(), 23); + assert_eq!(values[0], "2451545"); // ss_sold_date_sk + assert_eq!(values[2], "1"); // ss_sold_item_sk + assert_eq!(values[9], "1"); // ss_ticket_number + assert_eq!(values[10], "5"); // quantity + } + + #[test] + fn test_store_sales_row_null_handling() { + let pricing = create_test_pricing(); + // Set bit for ss_sold_time_sk (position 1) + let row = StoreSalesRow::new( + 0b10, // null_bit_map - second bit set + 2451545, 36000, 1, 100, 200, 300, 400, 500, 600, 1, pricing, + ); + + let values = row.get_values(); + assert_eq!(values[0], "2451545"); // not null + assert_eq!(values[1], ""); // null (bit 1 set) + } +} diff --git a/tpcdsgen/src/row/store_sales_row_generator.rs b/tpcdsgen/src/row/store_sales_row_generator.rs new file mode 100644 index 00000000..a59ecd66 --- /dev/null +++ b/tpcdsgen/src/row/store_sales_row_generator.rs @@ -0,0 +1,410 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Store sales row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::StoreSalesGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::permutations::{get_permutation_entry, make_permutation}; +use crate::random::RandomValueGenerator; +use crate::row::store_returns_row_generator::StoreReturnsRowGenerator; +use crate::row::store_sales_row::StoreSalesRow; +use crate::row::{AbstractRowGenerator, GeneratedRow, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::match_surrogate_key; +use crate::table::Table; +use crate::types::{generate_pricing_for_sales_table, get_store_sales_pricing_limits}; + +/// Percentage of sales that get returned +const SR_RETURN_PCT: i32 = 10; + +/// Order information shared across line items in the same order +struct OrderInfo { + ss_sold_store_sk: i64, + ss_sold_time_sk: i64, + ss_sold_date_sk: i64, + ss_sold_customer_sk: i64, + ss_sold_cdemo_sk: i64, + ss_sold_hdemo_sk: i64, + ss_sold_addr_sk: i64, + ss_ticket_number: i64, +} + +impl OrderInfo { + #[allow(clippy::too_many_arguments)] + fn new( + ss_sold_store_sk: i64, + ss_sold_time_sk: i64, + ss_sold_date_sk: i64, + ss_sold_customer_sk: i64, + ss_sold_cdemo_sk: i64, + ss_sold_hdemo_sk: i64, + ss_sold_addr_sk: i64, + ss_ticket_number: i64, + ) -> Self { + OrderInfo { + ss_sold_store_sk, + ss_sold_time_sk, + ss_sold_date_sk, + ss_sold_customer_sk, + ss_sold_cdemo_sk, + ss_sold_hdemo_sk, + ss_sold_addr_sk, + ss_ticket_number, + } + } + + fn default() -> Self { + OrderInfo { + ss_sold_store_sk: 0, + ss_sold_time_sk: 0, + ss_sold_date_sk: 0, + ss_sold_customer_sk: 0, + ss_sold_cdemo_sk: 0, + ss_sold_hdemo_sk: 0, + ss_sold_addr_sk: 0, + ss_ticket_number: 0, + } + } +} + +pub struct StoreSalesRowGenerator { + abstract_generator: AbstractRowGenerator, + item_permutation: Option>, + remaining_line_items: i32, + order_info: OrderInfo, + item_index: i32, + store_returns_generator: StoreReturnsRowGenerator, +} + +impl StoreSalesRowGenerator { + pub fn new() -> Self { + StoreSalesRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::StoreSales), + item_permutation: None, + remaining_line_items: 0, + order_info: OrderInfo::default(), + item_index: 0, + store_returns_generator: StoreReturnsRowGenerator::new(), + } + } + + fn generate_order_info(&mut self, row_number: i64, session: &Session) -> Result { + use StoreSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldStoreSk); + let ss_sold_store_sk = generate_join_key( + &SsSoldStoreSk, + stream, + crate::config::Table::Store, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldTimeSk); + let ss_sold_time_sk = generate_join_key( + &SsSoldTimeSk, + stream, + crate::config::Table::TimeDim, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldDateSk); + let ss_sold_date_sk = generate_join_key( + &SsSoldDateSk, + stream, + crate::config::Table::DateDim, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldCustomerSk); + let ss_sold_customer_sk = generate_join_key( + &SsSoldCustomerSk, + stream, + crate::config::Table::Customer, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldCdemoSk); + let ss_sold_cdemo_sk = generate_join_key( + &SsSoldCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldHdemoSk); + let ss_sold_hdemo_sk = generate_join_key( + &SsSoldHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldAddrSk); + let ss_sold_addr_sk = generate_join_key( + &SsSoldAddrSk, + stream, + crate::config::Table::CustomerAddress, + 1, + scaling, + )?; + + let ss_ticket_number = row_number; + + Ok(OrderInfo::new( + ss_sold_store_sk, + ss_sold_time_sk, + ss_sold_date_sk, + ss_sold_customer_sk, + ss_sold_cdemo_sk, + ss_sold_hdemo_sk, + ss_sold_addr_sk, + ss_ticket_number, + )) + } + + fn is_last_row_in_order(&self) -> bool { + self.remaining_line_items == 0 + } +} + +impl Default for StoreSalesRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl StoreSalesRowGenerator { + /// Consume remaining seeds for the child (store_returns) generator. + /// This should be called when shouldEndRow() is true, like Java's Results.rowStop() + pub fn consume_child_seeds(&mut self) { + self.store_returns_generator + .consume_remaining_seeds_for_row(); + } +} + +impl RowGenerator for StoreSalesRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + use StoreSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + let item_count = scaling.get_id_count(crate::config::Table::Item) as usize; + + // Initialize item permutation if needed + if self.item_permutation.is_none() { + let stream = self + .abstract_generator + .get_random_number_stream(&SsPermutation); + self.item_permutation = Some(make_permutation(item_count, stream)); + } + + // Start a new order if we've finished the previous one + if self.remaining_line_items == 0 { + self.order_info = self.generate_order_info(row_number, session)?; + + let stream = self + .abstract_generator + .get_random_number_stream(&SsTicketNumber); + self.remaining_line_items = + RandomValueGenerator::generate_uniform_random_int(8, 16, stream); + + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldItemSk); + self.item_index = + RandomValueGenerator::generate_uniform_random_int(1, item_count as i32, stream); + } + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&SsNulls); + let null_bit_map = create_null_bit_map(Table::StoreSales, stream); + + // Items need to be unique within an order + // Use a sequence within the permutation + self.item_index += 1; + if self.item_index > item_count as i32 { + self.item_index = 1; + } + + // Get item from permutation and match surrogate key for SCD + let permutation = self.item_permutation.as_ref().unwrap(); + let item_key = get_permutation_entry(permutation, self.item_index); + let ss_sold_item_sk = match_surrogate_key( + item_key as i64, + self.order_info.ss_sold_date_sk, + crate::config::Table::Item, + scaling, + ); + + // Generate promo sk + let stream = self + .abstract_generator + .get_random_number_stream(&SsSoldPromoSk); + let ss_sold_promo_sk = generate_join_key( + &SsSoldPromoSk, + stream, + crate::config::Table::Promotion, + 1, + scaling, + )?; + + // Generate pricing + let stream = self.abstract_generator.get_random_number_stream(&SsPricing); + let ss_pricing = + generate_pricing_for_sales_table(&get_store_sales_pricing_limits(), stream); + + let store_sales_row = StoreSalesRow::new( + null_bit_map, + self.order_info.ss_sold_date_sk, + self.order_info.ss_sold_time_sk, + ss_sold_item_sk, + self.order_info.ss_sold_customer_sk, + self.order_info.ss_sold_cdemo_sk, + self.order_info.ss_sold_hdemo_sk, + self.order_info.ss_sold_addr_sk, + self.order_info.ss_sold_store_sk, + ss_sold_promo_sk, + self.order_info.ss_ticket_number, + ss_pricing, + ); + + // Check if this sale gets returned (10% return rate) + // We check and generate the return BEFORE moving the sales row to avoid cloning + let stream = self + .abstract_generator + .get_random_number_stream(&SrIsReturned); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + + // Generate return row if applicable (using reference before we move sales_row) + // Note: In Java's --table store_sales mode, returns are NOT generated. + // This code generates returns (like Java's --table store_returns mode). + // The consume_remaining_seeds_for_row() is called separately in the binary. + let return_row = if random_int < SR_RETURN_PCT { + Some( + self.store_returns_generator + .generate_row(session, &store_sales_row)?, + ) + } else { + None + }; + + // Now move (not clone) the sales row into the result + let mut generated_rows: Vec = Vec::with_capacity(2); + generated_rows.push(store_sales_row.into()); + + if let Some(ret_row) = return_row { + generated_rows.push(ret_row); + } + + self.remaining_line_items -= 1; + + Ok(RowGeneratorResult::new_with_multiple( + generated_rows, + self.is_last_row_in_order(), + )) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::Session; + use crate::row::TableRow; + + #[test] + fn test_store_sales_row_generator_creation() { + let generator = StoreSalesRowGenerator::new(); + assert!(generator.item_permutation.is_none()); + assert_eq!(generator.remaining_line_items, 0); + } + + #[test] + fn test_store_sales_row_generation() { + let mut generator = StoreSalesRowGenerator::new(); + let session = Session::default(); + + let result = generator + .generate_row_and_child_rows(1, &session, None, None) + .unwrap(); + + // Should have at least one row (the store_sales row) + assert!(!result.get_rows().is_empty()); + + // First row should have 23 columns + let first_row = &result.get_rows()[0]; + assert_eq!(first_row.get_values().len(), 23); + } + + #[test] + fn test_store_sales_order_grouping() { + let mut generator = StoreSalesRowGenerator::new(); + let session = Session::default(); + + // Generate first row (starts new order) + let result1 = generator + .generate_row_and_child_rows(1, &session, None, None) + .unwrap(); + let values1 = result1.get_rows()[0].get_values(); + let ticket1 = &values1[9]; // ss_ticket_number + + // Generate second row (should be in same order) + let result2 = generator + .generate_row_and_child_rows(2, &session, None, None) + .unwrap(); + let values2 = result2.get_rows()[0].get_values(); + let ticket2 = &values2[9]; + + // Same ticket number means same order + assert_eq!(ticket1, ticket2); + } +} diff --git a/tpcdsgen/src/row/table_row.rs b/tpcdsgen/src/row/table_row.rs new file mode 100644 index 00000000..8b54044b --- /dev/null +++ b/tpcdsgen/src/row/table_row.rs @@ -0,0 +1,91 @@ +use std::io::{self, Write}; + +/// TableRow trait matching the Java TableRow interface +/// Represents a single row of data from any TPC-DS table +pub trait TableRow: Send + Sync { + /// Get all values as strings for output (getValues()) + /// + /// Note: This method allocates a `Vec`. For performance-critical code, + /// prefer using `write_to()` which writes directly to a buffer. + fn get_values(&self) -> Vec; + + /// Get the number of columns in this row + fn get_column_count(&self) -> usize { + self.get_values().len() + } + + /// Write the row directly to a writer, avoiding intermediate allocations. + /// + /// Each column value is separated by `separator`, and the row ends with + /// a trailing separator followed by a newline. + /// + /// Default implementation calls `get_values()` - override for better performance. + /// + /// Note: Uses `dyn Write` for trait object compatibility. The dynamic dispatch + /// overhead is negligible compared to I/O costs. + fn write_to(&self, writer: &mut dyn Write, separator: char) -> io::Result<()> { + let values = self.get_values(); + for (i, value) in values.iter().enumerate() { + if i > 0 { + write!(writer, "{}", separator)?; + } + write!(writer, "{}", value)?; + } + write!(writer, "{}", separator)?; + writeln!(writer) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Create a simple test implementation + struct TestTableRow { + values: Vec, + } + + impl TableRow for TestTableRow { + fn get_values(&self) -> Vec { + self.values.clone() + } + } + + #[test] + fn test_table_row_trait() { + let test_row = TestTableRow { + values: vec!["1".to_string(), "test".to_string(), "123.45".to_string()], + }; + + let values = test_row.get_values(); + assert_eq!(values.len(), 3); + assert_eq!(values[0], "1"); + assert_eq!(values[1], "test"); + assert_eq!(values[2], "123.45"); + assert_eq!(test_row.get_column_count(), 3); + } + + #[test] + fn test_write_to() { + let test_row = TestTableRow { + values: vec!["1".to_string(), "test".to_string(), "123.45".to_string()], + }; + + let mut buffer = Vec::new(); + test_row.write_to(&mut buffer, '|').unwrap(); + let output = String::from_utf8(buffer).unwrap(); + assert_eq!(output, "1|test|123.45|\n"); + } + + #[test] + fn test_write_to_empty_values() { + let test_row = TestTableRow { + values: vec!["".to_string(), "test".to_string(), "".to_string()], + }; + + let mut buffer = Vec::new(); + test_row.write_to(&mut buffer, '|').unwrap(); + let output = String::from_utf8(buffer).unwrap(); + assert_eq!(output, "|test||\n"); + } +} diff --git a/tpcdsgen/src/row/time_dim_row.rs b/tpcdsgen/src/row/time_dim_row.rs new file mode 100644 index 00000000..5e68804f --- /dev/null +++ b/tpcdsgen/src/row/time_dim_row.rs @@ -0,0 +1,95 @@ +use crate::row::TableRow; + +/// Represents a row in the TIME_DIM table +#[derive(Debug, Clone)] +pub struct TimeDimRow { + // Null bitmap for handling NULL values + null_bit_map: i64, + + // Primary key (seconds since midnight) + pub t_time_sk: i64, + + // Time identifier (HHMMSS format) + pub t_time_id: String, + + // Time value (seconds since midnight) + pub t_time: i32, + + // Time components + pub t_hour: i32, + pub t_minute: i32, + pub t_second: i32, + + // AM/PM indicator + pub t_am_pm: String, + + // Shift information + pub t_shift: String, + pub t_sub_shift: String, + + // Meal time classification + pub t_meal_time: String, +} + +impl TimeDimRow { + #[allow(clippy::too_many_arguments)] + /// Create a new TimeDimRow with all fields + pub fn new( + null_bit_map: i64, + t_time_sk: i64, + t_time_id: String, + t_time: i32, + t_hour: i32, + t_minute: i32, + t_second: i32, + t_am_pm: String, + t_shift: String, + t_sub_shift: String, + t_meal_time: String, + ) -> Self { + TimeDimRow { + null_bit_map, + t_time_sk, + t_time_id, + t_time, + t_hour, + t_minute, + t_second, + t_am_pm, + t_shift, + t_sub_shift, + t_meal_time, + } + } + + /// Check if a column should be NULL based on the null bitmap + fn is_field_null(&self, column_index: usize) -> bool { + (self.null_bit_map & (1 << column_index)) != 0 + } + + /// Get string value or NULL for optional fields + fn get_string_or_null(&self, value: T, column_index: usize) -> String { + if self.is_field_null(column_index) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for TimeDimRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null(self.t_time_sk, 0), + self.get_string_or_null(&self.t_time_id, 1), + self.get_string_or_null(self.t_time, 2), + self.get_string_or_null(self.t_hour, 3), + self.get_string_or_null(self.t_minute, 4), + self.get_string_or_null(self.t_second, 5), + self.get_string_or_null(&self.t_am_pm, 6), + self.get_string_or_null(&self.t_shift, 7), + self.get_string_or_null(&self.t_sub_shift, 8), + self.get_string_or_null(&self.t_meal_time, 9), + ] + } +} diff --git a/tpcdsgen/src/row/time_dim_row_generator.rs b/tpcdsgen/src/row/time_dim_row_generator.rs new file mode 100644 index 00000000..c9855f02 --- /dev/null +++ b/tpcdsgen/src/row/time_dim_row_generator.rs @@ -0,0 +1,82 @@ +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::distribution::HoursDistribution; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult, TimeDimRow}; +use crate::table::Table; + +pub struct TimeDimRowGenerator { + base: AbstractRowGenerator, +} + +impl Default for TimeDimRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl TimeDimRowGenerator { + pub fn new() -> Self { + TimeDimRowGenerator { + base: AbstractRowGenerator::new(Table::TimeDim), + } + } +} + +impl RowGenerator for TimeDimRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + _session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> crate::error::Result { + // Create null bitmap - TimeDim has very few nulls + let null_bit_map = 0i64; + + // Row number represents seconds since midnight (0-based) + let t_time_sk = row_number - 1; + let t_time_id = make_business_key(row_number); + let t_time = (row_number - 1) as i32; + + // Extract time components + let mut time_temp = t_time as i64; + let t_second = (time_temp % 60) as i32; + time_temp /= 60; + let t_minute = (time_temp % 60) as i32; + time_temp /= 60; + let t_hour = (time_temp % 24) as i32; + + // Get hour information for shift and meal time + let hour_info = HoursDistribution::get_hour_info_for_hour(t_hour); + let t_am_pm = hour_info.get_am_pm().to_string(); + let t_shift = hour_info.get_shift().to_string(); + let t_sub_shift = hour_info.get_sub_shift().to_string(); + let t_meal_time = hour_info.get_meal().to_string(); + + // Create the row + let row = TimeDimRow::new( + null_bit_map, + t_time_sk, + t_time_id, + t_time, + t_hour, + t_minute, + t_second, + t_am_pm, + t_shift, + t_sub_shift, + t_meal_time, + ); + + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.base.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.base + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/warehouse_row.rs b/tpcdsgen/src/row/warehouse_row.rs new file mode 100644 index 00000000..aea7557b --- /dev/null +++ b/tpcdsgen/src/row/warehouse_row.rs @@ -0,0 +1,99 @@ +use crate::row::TableRow; +use crate::types::Address; + +/// Warehouse table row (WarehouseRow) +#[derive(Debug, Clone)] +pub struct WarehouseRow { + null_bit_map: i64, + w_warehouse_sk: i64, + w_warehouse_id: String, + w_warehouse_name: String, + w_warehouse_sq_ft: i32, + w_address: Address, +} + +impl WarehouseRow { + pub fn new( + null_bit_map: i64, + w_warehouse_sk: i64, + w_warehouse_id: String, + w_warehouse_name: String, + w_warehouse_sq_ft: i32, + w_address: Address, + ) -> Self { + WarehouseRow { + null_bit_map, + w_warehouse_sk, + w_warehouse_id, + w_warehouse_name, + w_warehouse_sq_ft, + w_address, + } + } + + /// Check if a column should be null based on the null bitmap (TableRowWithNulls logic) + fn should_be_null(&self, column_position: i32) -> bool { + ((self.null_bit_map >> column_position) & 1) == 1 + } + + /// Convert value to string or empty string if null (getStringOrNull) + fn get_string_or_null(&self, value: T, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + /// Convert key to string or empty string if null (getStringOrNullForKey) + fn get_string_or_null_for_key(&self, value: i64, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + value.to_string() + } + } + + pub fn get_w_warehouse_sk(&self) -> i64 { + self.w_warehouse_sk + } + + pub fn get_w_warehouse_id(&self) -> &str { + &self.w_warehouse_id + } + + pub fn get_w_warehouse_name(&self) -> &str { + &self.w_warehouse_name + } + + pub fn get_w_warehouse_sq_ft(&self) -> i32 { + self.w_warehouse_sq_ft + } + + pub fn get_w_address(&self) -> &Address { + &self.w_address + } +} + +impl TableRow for WarehouseRow { + fn get_values(&self) -> Vec { + // Column positions match Java WarehouseGeneratorColumn + // First column (W_WAREHOUSE_SK) is at global position 351, so relative positions are 0-13 + vec![ + self.get_string_or_null_for_key(self.w_warehouse_sk, 0), + self.get_string_or_null(&self.w_warehouse_id, 1), + self.get_string_or_null(&self.w_warehouse_name, 2), + self.get_string_or_null(self.w_warehouse_sq_ft, 3), + self.get_string_or_null(self.w_address.get_street_number(), 4), + self.get_string_or_null(self.w_address.get_street_name(), 5), + self.get_string_or_null(self.w_address.get_street_type(), 6), + self.get_string_or_null(self.w_address.get_suite_number(), 7), + self.get_string_or_null(self.w_address.get_city(), 8), + self.get_string_or_null(self.w_address.get_county().unwrap_or(""), 9), + self.get_string_or_null(self.w_address.get_state(), 10), + self.get_string_or_null(format!("{:05}", self.w_address.get_zip()), 11), + self.get_string_or_null(self.w_address.get_country(), 12), + self.get_string_or_null(self.w_address.get_gmt_offset(), 13), + ] + } +} diff --git a/tpcdsgen/src/row/warehouse_row_generator.rs b/tpcdsgen/src/row/warehouse_row_generator.rs new file mode 100644 index 00000000..c894060e --- /dev/null +++ b/tpcdsgen/src/row/warehouse_row_generator.rs @@ -0,0 +1,101 @@ +use crate::business_key_generator::make_business_key; +use crate::config::Session; +use crate::error::Result; +use crate::generator::WarehouseGeneratorColumn; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult, WarehouseRow}; +use crate::table::Table; +use crate::types::Address; + +/// Row generator for the WAREHOUSE table (WarehouseRowGenerator) +pub struct WarehouseRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl Default for WarehouseRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl WarehouseRowGenerator { + /// Create a new WarehouseRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::Warehouse), + } + } + + /// Generate a WarehouseRow with realistic data following Java implementation + fn generate_warehouse_row( + &mut self, + row_number: i64, + session: &Session, + ) -> Result { + // Create null bit map (createNullBitMap call) + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&WarehouseGeneratorColumn::WNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = RandomValueGenerator::generate_uniform_random_int(1, i32::MAX, nulls_stream); + + // Calculate null_bit_map based on threshold and table's not-null bitmap (Nulls.createNullBitMap) + let null_bit_map = if threshold < Table::Warehouse.get_null_basis_points() { + (bit_map as i64) & !Table::Warehouse.get_not_null_bit_map() + } else { + 0 + }; + + let w_warehouse_sk = row_number; + let w_warehouse_id = make_business_key(row_number); + + let name_stream = self + .abstract_generator + .get_random_number_stream(&WarehouseGeneratorColumn::WWarehouseName); + let w_warehouse_name = RandomValueGenerator::generate_random_text(10, 20, name_stream); + + let sq_ft_stream = self + .abstract_generator + .get_random_number_stream(&WarehouseGeneratorColumn::WWarehouseSqFt); + let w_warehouse_sq_ft = + RandomValueGenerator::generate_uniform_random_int(50000, 1000000, sq_ft_stream); + + let scaling = session.get_scaling(); + let address_stream = self + .abstract_generator + .get_random_number_stream(&WarehouseGeneratorColumn::WWarehouseAddress); + let w_address = + Address::make_address_for_column(Table::Warehouse, address_stream, scaling)?; + + Ok(WarehouseRow::new( + null_bit_map, + w_warehouse_sk, + w_warehouse_id, + w_warehouse_name, + w_warehouse_sq_ft, + w_address, + )) + } +} + +impl RowGenerator for WarehouseRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_warehouse_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/web_page_row.rs b/tpcdsgen/src/row/web_page_row.rs new file mode 100644 index 00000000..4fe31016 --- /dev/null +++ b/tpcdsgen/src/row/web_page_row.rs @@ -0,0 +1,172 @@ +use crate::row::TableRow; +use crate::types::Date; + +/// Row structure for the WEB_PAGE table (WebPageRow) +#[derive(Debug, Clone)] +pub struct WebPageRow { + null_bit_map: i64, + wp_page_sk: i64, + wp_page_id: String, + wp_rec_start_date_id: i64, + wp_rec_end_date_id: i64, + wp_creation_date_sk: i64, + wp_access_date_sk: i64, + wp_autogen_flag: bool, + wp_customer_sk: i64, + wp_url: String, + wp_type: String, + wp_char_count: i32, + wp_link_count: i32, + wp_image_count: i32, + wp_max_ad_count: i32, +} + +impl WebPageRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + wp_page_sk: i64, + wp_page_id: String, + wp_rec_start_date_id: i64, + wp_rec_end_date_id: i64, + wp_creation_date_sk: i64, + wp_access_date_sk: i64, + wp_autogen_flag: bool, + wp_customer_sk: i64, + wp_url: String, + wp_type: String, + wp_char_count: i32, + wp_link_count: i32, + wp_image_count: i32, + wp_max_ad_count: i32, + ) -> Self { + Self { + null_bit_map, + wp_page_sk, + wp_page_id, + wp_rec_start_date_id, + wp_rec_end_date_id, + wp_creation_date_sk, + wp_access_date_sk, + wp_autogen_flag, + wp_customer_sk, + wp_url, + wp_type, + wp_char_count, + wp_link_count, + wp_image_count, + wp_max_ad_count, + } + } + + // Getters for SCD comparison (needed in WebPageRowGenerator) + pub fn get_wp_creation_date_sk(&self) -> i64 { + self.wp_creation_date_sk + } + + pub fn get_wp_access_date_sk(&self) -> i64 { + self.wp_access_date_sk + } + + pub fn get_wp_autogen_flag(&self) -> bool { + self.wp_autogen_flag + } + + pub fn get_wp_customer_sk(&self) -> i64 { + self.wp_customer_sk + } + + pub fn get_wp_char_count(&self) -> i32 { + self.wp_char_count + } + + pub fn get_wp_link_count(&self) -> i32 { + self.wp_link_count + } + + pub fn get_wp_image_count(&self) -> i32 { + self.wp_image_count + } + + pub fn get_wp_max_ad_count(&self) -> i32 { + self.wp_max_ad_count + } + + /// Check if a column should be null based on the null bit map (shouldBeNull) + fn should_be_null(&self, column_position: i32) -> bool { + (self.null_bit_map & (1 << column_position)) != 0 + } + + /// Convert optional value to string or empty string if null (getStringOrNull) + fn get_string_or_null( + &self, + value: Option<&T>, + column_position: i32, + ) -> String { + if self.should_be_null(column_position) { + String::new() + } else { + match value { + Some(v) => v.to_string(), + None => String::new(), + } + } + } + + /// Convert key to string or empty string if null (getStringOrNullForKey) + /// Returns empty if null OR if value is -1 + fn get_string_or_null_for_key(&self, value: i64, column_position: i32) -> String { + if self.should_be_null(column_position) || value == -1 { + String::new() + } else { + value.to_string() + } + } + + /// Convert boolean to Y/N string or empty string if null (getStringOrNullForBoolean) + fn get_string_or_null_for_boolean(&self, value: bool, column_position: i32) -> String { + if self.should_be_null(column_position) { + String::new() + } else if value { + "Y".to_string() + } else { + "N".to_string() + } + } + + /// Convert julian date to date string or empty string if null (getDateStringOrNullFromJulianDays) + /// Returns empty if null OR if value is negative + fn get_date_string_or_null_from_julian_days( + &self, + julian_days: i64, + column_position: i32, + ) -> String { + if self.should_be_null(column_position) || julian_days < 0 { + String::new() + } else { + let date = Date::from_julian_days(julian_days as i32); + date.to_string() + } + } +} + +impl TableRow for WebPageRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null_for_key(self.wp_page_sk, 0), + self.get_string_or_null(Some(&self.wp_page_id), 1), + self.get_date_string_or_null_from_julian_days(self.wp_rec_start_date_id, 2), + self.get_date_string_or_null_from_julian_days(self.wp_rec_end_date_id, 3), + self.get_string_or_null_for_key(self.wp_creation_date_sk, 4), + self.get_string_or_null_for_key(self.wp_access_date_sk, 5), + self.get_string_or_null_for_boolean(self.wp_autogen_flag, 6), + self.get_string_or_null_for_key(self.wp_customer_sk, 7), + self.get_string_or_null(Some(&self.wp_url), 8), + self.get_string_or_null(Some(&self.wp_type), 9), + self.get_string_or_null(Some(&self.wp_char_count.to_string()), 10), + self.get_string_or_null(Some(&self.wp_link_count.to_string()), 11), + self.get_string_or_null(Some(&self.wp_image_count.to_string()), 12), + self.get_string_or_null(Some(&self.wp_max_ad_count.to_string()), 13), + ] + } +} diff --git a/tpcdsgen/src/row/web_page_row_generator.rs b/tpcdsgen/src/row/web_page_row_generator.rs new file mode 100644 index 00000000..0bd47b48 --- /dev/null +++ b/tpcdsgen/src/row/web_page_row_generator.rs @@ -0,0 +1,283 @@ +use crate::config::{Session, Table as ConfigTable}; +use crate::distribution::web_page_use_distribution::WebPageUseDistribution; +use crate::error::Result; +use crate::generator::WebPageGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult, WebPageRow}; +use crate::slowly_changing_dimension_utils::{ + compute_scd_key, get_value_for_slowly_changing_dimension, +}; +use crate::table::Table; +use crate::types::Date; + +/// Row generator for the WEB_PAGE table (WebPageRowGenerator) +/// Pattern 2: SCD table with slowly changing dimension logic +pub struct WebPageRowGenerator { + abstract_generator: AbstractRowGenerator, + previous_row: Option, +} + +impl Default for WebPageRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl WebPageRowGenerator { + const WP_AUTOGEN_PERCENT: i32 = 30; + + /// Create a new WebPageRowGenerator + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::WebPage), + previous_row: None, + } + } + + /// Generate a WebPageRow with SCD logic following Java implementation + fn generate_web_page_row(&mut self, row_number: i64, session: &Session) -> Result { + // Create null bit map + let nulls_stream = self + .abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpNulls); + let threshold = RandomValueGenerator::generate_uniform_random_int(0, 9999, nulls_stream); + let bit_map = RandomValueGenerator::generate_uniform_random_int(1, i32::MAX, nulls_stream); + + let null_bit_map = if threshold < Table::WebPage.get_null_basis_points() { + (bit_map as i64) & !Table::WebPage.get_not_null_bit_map() + } else { + 0 + }; + + let wp_page_sk = row_number; + + // Compute SCD key information + let scd_key = compute_scd_key(Table::WebPage, row_number); + let wp_page_id = scd_key.get_business_key().to_string(); + let wp_rec_start_date_id = scd_key.get_start_date(); + let wp_rec_end_date_id = scd_key.get_end_date(); + let is_new_key = scd_key.is_new_business_key(); + + // Get field change flags for SCD + let mut field_change_flags = self + .abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpScd) + .next_random() as i32; + + // wp_creation_date_sk - join to DATE_DIM + let mut wp_creation_date_sk = generate_join_key( + &WebPageGeneratorColumn::WpCreationDateSk, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpCreationDateSk), + ConfigTable::DateDim, + row_number, + session.get_scaling(), + )?; + if let Some(prev) = &self.previous_row { + wp_creation_date_sk = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_creation_date_sk(), + wp_creation_date_sk, + ); + } + field_change_flags >>= 1; + + // wp_access_date_sk - today's date minus last access days + let last_access = RandomValueGenerator::generate_uniform_random_int( + 0, + 100, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpAccessDateSk), + ); + let mut wp_access_date_sk = Date::JULIAN_TODAYS_DATE as i64 - last_access as i64; + if let Some(prev) = &self.previous_row { + wp_access_date_sk = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_access_date_sk(), + wp_access_date_sk, + ); + } + field_change_flags >>= 1; + + // wp_autogen_flag - 30% chance of auto-generated + let random_int = RandomValueGenerator::generate_uniform_random_int( + 0, + 99, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpAutogenFlag), + ); + let mut wp_autogen_flag = random_int < Self::WP_AUTOGEN_PERCENT; + if let Some(prev) = &self.previous_row { + wp_autogen_flag = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_autogen_flag(), + wp_autogen_flag, + ); + } + field_change_flags >>= 1; + + // wp_customer_sk - join to CUSTOMER + let mut wp_customer_sk = generate_join_key( + &WebPageGeneratorColumn::WpCustomerSk, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpCustomerSk), + ConfigTable::Customer, + 1, + session.get_scaling(), + )?; + if let Some(prev) = &self.previous_row { + wp_customer_sk = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_customer_sk(), + wp_customer_sk, + ); + } + field_change_flags >>= 1; + + // wp_url - always returns the same value, so no need to check if it should change + let wp_url = RandomValueGenerator::generate_random_url( + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpUrl), + ); + field_change_flags >>= 1; + + // wp_type - always uses a new value due to a bug in the C code + let wp_type = WebPageUseDistribution::pick_random_web_page_use_type( + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpType), + )?; + field_change_flags >>= 1; + + // wp_link_count + let mut wp_link_count = RandomValueGenerator::generate_uniform_random_int( + 2, + 25, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpLinkCount), + ); + if let Some(prev) = &self.previous_row { + wp_link_count = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_link_count(), + wp_link_count, + ); + } + field_change_flags >>= 1; + + // wp_image_count + let mut wp_image_count = RandomValueGenerator::generate_uniform_random_int( + 1, + 7, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpImageCount), + ); + if let Some(prev) = &self.previous_row { + wp_image_count = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_image_count(), + wp_image_count, + ); + } + field_change_flags >>= 1; + + // wp_max_ad_count + let mut wp_max_ad_count = RandomValueGenerator::generate_uniform_random_int( + 0, + 4, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpMaxAdCount), + ); + if let Some(prev) = &self.previous_row { + wp_max_ad_count = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_max_ad_count(), + wp_max_ad_count, + ); + } + field_change_flags >>= 1; + + // wp_char_count - calculated based on link and image counts + let mut wp_char_count = RandomValueGenerator::generate_uniform_random_int( + wp_link_count * 125 + wp_image_count * 50, + wp_link_count * 300 + wp_image_count * 150, + self.abstract_generator + .get_random_number_stream(&WebPageGeneratorColumn::WpCharCount), + ); + if let Some(prev) = &self.previous_row { + wp_char_count = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_key, + prev.get_wp_char_count(), + wp_char_count, + ); + } + + // Store current row for next iteration (before creating output row) + self.previous_row = Some(WebPageRow::new( + null_bit_map, + wp_page_sk, + wp_page_id.clone(), + wp_rec_start_date_id, + wp_rec_end_date_id, + wp_creation_date_sk, + wp_access_date_sk, + wp_autogen_flag, + wp_customer_sk, + wp_url.clone(), + wp_type.clone(), + wp_char_count, + wp_link_count, + wp_image_count, + wp_max_ad_count, + )); + + // Return row with wp_customer_sk set to -1 if not autogenerated (line 155 in Java) + Ok(WebPageRow::new( + null_bit_map, + wp_page_sk, + wp_page_id, + wp_rec_start_date_id, + wp_rec_end_date_id, + wp_creation_date_sk, + wp_access_date_sk, + wp_autogen_flag, + if wp_autogen_flag { wp_customer_sk } else { -1 }, + wp_url, + wp_type, + wp_char_count, + wp_link_count, + wp_image_count, + wp_max_ad_count, + )) + } +} + +impl RowGenerator for WebPageRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + let row = self.generate_web_page_row(row_number, session)?; + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/web_returns_row.rs b/tpcdsgen/src/row/web_returns_row.rs new file mode 100644 index 00000000..fdc74fc9 --- /dev/null +++ b/tpcdsgen/src/row/web_returns_row.rs @@ -0,0 +1,145 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web returns row definition + +use crate::generator::{GeneratorColumn, WebReturnsGeneratorColumn}; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row structure for web_returns table +#[derive(Debug, Clone)] +pub struct WebReturnsRow { + null_bit_map: i64, + wr_returned_date_sk: i64, + wr_returned_time_sk: i64, + wr_item_sk: i64, + wr_refunded_customer_sk: i64, + wr_refunded_cdemo_sk: i64, + wr_refunded_hdemo_sk: i64, + wr_refunded_addr_sk: i64, + wr_returning_customer_sk: i64, + wr_returning_cdemo_sk: i64, + wr_returning_hdemo_sk: i64, + wr_returning_addr_sk: i64, + wr_web_page_sk: i64, + wr_reason_sk: i64, + wr_order_number: i64, + wr_pricing: Pricing, +} + +impl WebReturnsRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + wr_returned_date_sk: i64, + wr_returned_time_sk: i64, + wr_item_sk: i64, + wr_refunded_customer_sk: i64, + wr_refunded_cdemo_sk: i64, + wr_refunded_hdemo_sk: i64, + wr_refunded_addr_sk: i64, + wr_returning_customer_sk: i64, + wr_returning_cdemo_sk: i64, + wr_returning_hdemo_sk: i64, + wr_returning_addr_sk: i64, + wr_web_page_sk: i64, + wr_reason_sk: i64, + wr_order_number: i64, + wr_pricing: Pricing, + ) -> Self { + WebReturnsRow { + null_bit_map, + wr_returned_date_sk, + wr_returned_time_sk, + wr_item_sk, + wr_refunded_customer_sk, + wr_refunded_cdemo_sk, + wr_refunded_hdemo_sk, + wr_refunded_addr_sk, + wr_returning_customer_sk, + wr_returning_cdemo_sk, + wr_returning_hdemo_sk, + wr_returning_addr_sk, + wr_web_page_sk, + wr_reason_sk, + wr_order_number, + wr_pricing, + } + } + + fn is_null(&self, column: WebReturnsGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - WebReturnsGeneratorColumn::WrReturnedDateSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key(&self, value: i64, column: WebReturnsGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null( + &self, + value: T, + column: WebReturnsGeneratorColumn, + ) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for WebReturnsRow { + fn get_values(&self) -> Vec { + use WebReturnsGeneratorColumn::*; + vec![ + self.get_string_or_null_for_key(self.wr_returned_date_sk, WrReturnedDateSk), + self.get_string_or_null_for_key(self.wr_returned_time_sk, WrReturnedTimeSk), + self.get_string_or_null_for_key(self.wr_item_sk, WrItemSk), + self.get_string_or_null_for_key(self.wr_refunded_customer_sk, WrRefundedCustomerSk), + self.get_string_or_null_for_key(self.wr_refunded_cdemo_sk, WrRefundedCdemoSk), + self.get_string_or_null_for_key(self.wr_refunded_hdemo_sk, WrRefundedHdemoSk), + self.get_string_or_null_for_key(self.wr_refunded_addr_sk, WrRefundedAddrSk), + self.get_string_or_null_for_key(self.wr_returning_customer_sk, WrReturningCustomerSk), + self.get_string_or_null_for_key(self.wr_returning_cdemo_sk, WrReturningCdemoSk), + self.get_string_or_null_for_key(self.wr_returning_hdemo_sk, WrReturningHdemoSk), + self.get_string_or_null_for_key(self.wr_returning_addr_sk, WrReturningAddrSk), + self.get_string_or_null_for_key(self.wr_web_page_sk, WrWebPageSk), + self.get_string_or_null_for_key(self.wr_reason_sk, WrReasonSk), + self.get_string_or_null_for_key(self.wr_order_number, WrOrderNumber), + self.get_string_or_null(self.wr_pricing.get_quantity(), WrPricingQuantity), + self.get_string_or_null(self.wr_pricing.get_net_paid(), WrPricingNetPaid), + self.get_string_or_null(self.wr_pricing.get_ext_tax(), WrPricingExtTax), + self.get_string_or_null( + self.wr_pricing.get_net_paid_including_tax(), + WrPricingNetPaidIncTax, + ), + self.get_string_or_null(self.wr_pricing.get_fee(), WrPricingFee), + self.get_string_or_null(self.wr_pricing.get_ext_ship_cost(), WrPricingExtShipCost), + self.get_string_or_null(self.wr_pricing.get_refunded_cash(), WrPricingRefundedCash), + self.get_string_or_null( + self.wr_pricing.get_reversed_charge(), + WrPricingReversedCharge, + ), + self.get_string_or_null(self.wr_pricing.get_store_credit(), WrPricingStoreCredit), + self.get_string_or_null(self.wr_pricing.get_net_loss(), WrPricingNetLoss), + ] + } +} diff --git a/tpcdsgen/src/row/web_returns_row_generator.rs b/tpcdsgen/src/row/web_returns_row_generator.rs new file mode 100644 index 00000000..59939636 --- /dev/null +++ b/tpcdsgen/src/row/web_returns_row_generator.rs @@ -0,0 +1,199 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web returns row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::WebReturnsGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::web_returns_row::WebReturnsRow; +use crate::row::web_sales_row::WebSalesRow; +use crate::row::web_sales_row_generator::GIFT_PERCENTAGE; +use crate::row::{AbstractRowGenerator, GeneratedRow}; +use crate::table::Table; +use crate::types::generate_pricing_for_returns_table; + +pub struct WebReturnsRowGenerator { + abstract_generator: AbstractRowGenerator, +} + +impl WebReturnsRowGenerator { + pub fn new() -> Self { + WebReturnsRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::WebReturns), + } + } + + pub fn generate_row( + &mut self, + session: &Session, + sales_row: &WebSalesRow, + ) -> Result { + use WebReturnsGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&WrNulls); + let null_bit_map = create_null_bit_map(Table::WebReturns, stream); + + // Fields taken from the original sale + let wr_item_sk = sales_row.get_ws_item_sk(); + let wr_order_number = sales_row.get_ws_order_number(); + let wr_web_page_sk = sales_row.get_ws_web_page_sk(); + + // Remaining fields are specific to this return + let stream = self + .abstract_generator + .get_random_number_stream(&WrReturnedDateSk); + let wr_returned_date_sk = generate_join_key( + &WrReturnedDateSk, + stream, + crate::config::Table::DateDim, + sales_row.get_ws_ship_date_sk(), + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WrReturnedTimeSk); + let wr_returned_time_sk = generate_join_key( + &WrReturnedTimeSk, + stream, + crate::config::Table::TimeDim, + 1, + scaling, + )?; + + // Items are usually returned to the people they were shipped to, but sometimes not + // Generate new values first + let stream = self + .abstract_generator + .get_random_number_stream(&WrRefundedCustomerSk); + let mut wr_refunded_customer_sk = generate_join_key( + &WrRefundedCustomerSk, + stream, + crate::config::Table::Customer, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WrRefundedCdemoSk); + let mut wr_refunded_cdemo_sk = generate_join_key( + &WrRefundedCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WrRefundedHdemoSk); + let mut wr_refunded_hdemo_sk = generate_join_key( + &WrRefundedHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WrRefundedAddrSk); + let mut wr_refunded_addr_sk = generate_join_key( + &WrRefundedAddrSk, + stream, + crate::config::Table::CustomerAddress, + 1, + scaling, + )?; + + // If below GIFT_PERCENTAGE, use ship info from sales row instead + let stream = self + .abstract_generator + .get_random_number_stream(&WrReturningCustomerSk); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + if random_int < GIFT_PERCENTAGE { + wr_refunded_customer_sk = sales_row.get_ws_ship_customer_sk(); + wr_refunded_cdemo_sk = sales_row.get_ws_ship_cdemo_sk(); + wr_refunded_hdemo_sk = sales_row.get_ws_ship_hdemo_sk(); + wr_refunded_addr_sk = sales_row.get_ws_ship_addr_sk(); + } + + // Returning customer is same as refunded customer + let wr_returning_customer_sk = wr_refunded_customer_sk; + let wr_returning_cdemo_sk = wr_refunded_cdemo_sk; + let wr_returning_hdemo_sk = wr_refunded_hdemo_sk; + let wr_returning_addr_sk = wr_refunded_addr_sk; + + let stream = self + .abstract_generator + .get_random_number_stream(&WrReasonSk); + let wr_reason_sk = generate_join_key( + &WrReasonSk, + stream, + crate::config::Table::Reason, + 1, + scaling, + )?; + + // Generate pricing for returns + let stream = self.abstract_generator.get_random_number_stream(&WrPricing); + let quantity = RandomValueGenerator::generate_uniform_random_int( + 1, + sales_row.get_ws_pricing().get_quantity(), + stream, + ); + + let stream = self.abstract_generator.get_random_number_stream(&WrPricing); + let wr_pricing = + generate_pricing_for_returns_table(stream, quantity, sales_row.get_ws_pricing()); + + Ok(WebReturnsRow::new( + null_bit_map, + wr_returned_date_sk, + wr_returned_time_sk, + wr_item_sk, + wr_refunded_customer_sk, + wr_refunded_cdemo_sk, + wr_refunded_hdemo_sk, + wr_refunded_addr_sk, + wr_returning_customer_sk, + wr_returning_cdemo_sk, + wr_returning_hdemo_sk, + wr_returning_addr_sk, + wr_web_page_sk, + wr_reason_sk, + wr_order_number, + wr_pricing, + ) + .into()) + } + + pub fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } +} + +impl Default for WebReturnsRowGenerator { + fn default() -> Self { + Self::new() + } +} diff --git a/tpcdsgen/src/row/web_sales_row.rs b/tpcdsgen/src/row/web_sales_row.rs new file mode 100644 index 00000000..4816cf28 --- /dev/null +++ b/tpcdsgen/src/row/web_sales_row.rs @@ -0,0 +1,215 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web sales row definition + +use crate::generator::{GeneratorColumn, WebSalesGeneratorColumn}; +use crate::row::TableRow; +use crate::types::Pricing; + +/// Row structure for web_sales table +#[derive(Debug, Clone)] +pub struct WebSalesRow { + null_bit_map: i64, + ws_sold_date_sk: i64, + ws_sold_time_sk: i64, + ws_ship_date_sk: i64, + ws_item_sk: i64, + ws_bill_customer_sk: i64, + ws_bill_cdemo_sk: i64, + ws_bill_hdemo_sk: i64, + ws_bill_addr_sk: i64, + ws_ship_customer_sk: i64, + ws_ship_cdemo_sk: i64, + ws_ship_hdemo_sk: i64, + ws_ship_addr_sk: i64, + ws_web_page_sk: i64, + ws_web_site_sk: i64, + ws_ship_mode_sk: i64, + ws_warehouse_sk: i64, + ws_promo_sk: i64, + ws_order_number: i64, + ws_pricing: Pricing, +} + +impl WebSalesRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + ws_sold_date_sk: i64, + ws_sold_time_sk: i64, + ws_ship_date_sk: i64, + ws_item_sk: i64, + ws_bill_customer_sk: i64, + ws_bill_cdemo_sk: i64, + ws_bill_hdemo_sk: i64, + ws_bill_addr_sk: i64, + ws_ship_customer_sk: i64, + ws_ship_cdemo_sk: i64, + ws_ship_hdemo_sk: i64, + ws_ship_addr_sk: i64, + ws_web_page_sk: i64, + ws_web_site_sk: i64, + ws_ship_mode_sk: i64, + ws_warehouse_sk: i64, + ws_promo_sk: i64, + ws_order_number: i64, + ws_pricing: Pricing, + ) -> Self { + WebSalesRow { + null_bit_map, + ws_sold_date_sk, + ws_sold_time_sk, + ws_ship_date_sk, + ws_item_sk, + ws_bill_customer_sk, + ws_bill_cdemo_sk, + ws_bill_hdemo_sk, + ws_bill_addr_sk, + ws_ship_customer_sk, + ws_ship_cdemo_sk, + ws_ship_hdemo_sk, + ws_ship_addr_sk, + ws_web_page_sk, + ws_web_site_sk, + ws_ship_mode_sk, + ws_warehouse_sk, + ws_promo_sk, + ws_order_number, + ws_pricing, + } + } + + pub fn get_ws_item_sk(&self) -> i64 { + self.ws_item_sk + } + + pub fn get_ws_order_number(&self) -> i64 { + self.ws_order_number + } + + pub fn get_ws_web_page_sk(&self) -> i64 { + self.ws_web_page_sk + } + + pub fn get_ws_ship_date_sk(&self) -> i64 { + self.ws_ship_date_sk + } + + pub fn get_ws_ship_customer_sk(&self) -> i64 { + self.ws_ship_customer_sk + } + + pub fn get_ws_ship_cdemo_sk(&self) -> i64 { + self.ws_ship_cdemo_sk + } + + pub fn get_ws_ship_hdemo_sk(&self) -> i64 { + self.ws_ship_hdemo_sk + } + + pub fn get_ws_ship_addr_sk(&self) -> i64 { + self.ws_ship_addr_sk + } + + pub fn get_ws_pricing(&self) -> &Pricing { + &self.ws_pricing + } + + fn is_null(&self, column: WebSalesGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - WebSalesGeneratorColumn::WsSoldDateSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } + + fn get_string_or_null_for_key(&self, value: i64, column: WebSalesGeneratorColumn) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null( + &self, + value: T, + column: WebSalesGeneratorColumn, + ) -> String { + if self.is_null(column) { + String::new() + } else { + value.to_string() + } + } +} + +impl TableRow for WebSalesRow { + fn get_values(&self) -> Vec { + use WebSalesGeneratorColumn::*; + vec![ + self.get_string_or_null_for_key(self.ws_sold_date_sk, WsSoldDateSk), + self.get_string_or_null_for_key(self.ws_sold_time_sk, WsSoldTimeSk), + self.get_string_or_null_for_key(self.ws_ship_date_sk, WsShipDateSk), + self.get_string_or_null_for_key(self.ws_item_sk, WsItemSk), + self.get_string_or_null_for_key(self.ws_bill_customer_sk, WsBillCustomerSk), + self.get_string_or_null_for_key(self.ws_bill_cdemo_sk, WsBillCdemoSk), + self.get_string_or_null_for_key(self.ws_bill_hdemo_sk, WsBillHdemoSk), + self.get_string_or_null_for_key(self.ws_bill_addr_sk, WsBillAddrSk), + self.get_string_or_null_for_key(self.ws_ship_customer_sk, WsShipCustomerSk), + self.get_string_or_null_for_key(self.ws_ship_cdemo_sk, WsShipCdemoSk), + self.get_string_or_null_for_key(self.ws_ship_hdemo_sk, WsShipHdemoSk), + self.get_string_or_null_for_key(self.ws_ship_addr_sk, WsShipAddrSk), + self.get_string_or_null_for_key(self.ws_web_page_sk, WsWebPageSk), + self.get_string_or_null_for_key(self.ws_web_site_sk, WsWebSiteSk), + self.get_string_or_null_for_key(self.ws_ship_mode_sk, WsShipModeSk), + self.get_string_or_null_for_key(self.ws_warehouse_sk, WsWarehouseSk), + self.get_string_or_null_for_key(self.ws_promo_sk, WsPromoSk), + self.get_string_or_null_for_key(self.ws_order_number, WsOrderNumber), + self.get_string_or_null(self.ws_pricing.get_quantity(), WsPricingQuantity), + self.get_string_or_null(self.ws_pricing.get_wholesale_cost(), WsPricingWholesaleCost), + self.get_string_or_null(self.ws_pricing.get_list_price(), WsPricingListPrice), + self.get_string_or_null(self.ws_pricing.get_sales_price(), WsPricingSalesPrice), + self.get_string_or_null( + self.ws_pricing.get_ext_discount_amount(), + WsPricingExtDiscountAmt, + ), + self.get_string_or_null( + self.ws_pricing.get_ext_sales_price(), + WsPricingExtSalesPrice, + ), + self.get_string_or_null( + self.ws_pricing.get_ext_wholesale_cost(), + WsPricingExtWholesaleCost, + ), + self.get_string_or_null(self.ws_pricing.get_ext_list_price(), WsPricingExtListPrice), + self.get_string_or_null(self.ws_pricing.get_ext_tax(), WsPricingExtTax), + self.get_string_or_null(self.ws_pricing.get_coupon_amount(), WsPricingCouponAmt), + self.get_string_or_null(self.ws_pricing.get_ext_ship_cost(), WsPricingExtShipCost), + self.get_string_or_null(self.ws_pricing.get_net_paid(), WsPricingNetPaid), + self.get_string_or_null( + self.ws_pricing.get_net_paid_including_tax(), + WsPricingNetPaidIncTax, + ), + self.get_string_or_null( + self.ws_pricing.get_net_paid_including_shipping(), + WsPricingNetPaidIncShip, + ), + self.get_string_or_null( + self.ws_pricing.get_net_paid_including_shipping_and_tax(), + WsPricingNetPaidIncShipTax, + ), + self.get_string_or_null(self.ws_pricing.get_net_profit(), WsPricingNetProfit), + ] + } +} diff --git a/tpcdsgen/src/row/web_sales_row_generator.rs b/tpcdsgen/src/row/web_sales_row_generator.rs new file mode 100644 index 00000000..e185c3c7 --- /dev/null +++ b/tpcdsgen/src/row/web_sales_row_generator.rs @@ -0,0 +1,445 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Web sales row generator + +use crate::config::Session; +use crate::error::Result; +use crate::generator::WebSalesGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::permutations::{get_permutation_entry, make_permutation}; +use crate::random::RandomValueGenerator; +use crate::row::web_returns_row_generator::WebReturnsRowGenerator; +use crate::row::web_sales_row::WebSalesRow; +use crate::row::{AbstractRowGenerator, GeneratedRow, RowGenerator, RowGeneratorResult}; +use crate::slowly_changing_dimension_utils::match_surrogate_key; +use crate::table::Table; +use crate::types::{generate_pricing_for_sales_table, get_web_sales_pricing_limits}; + +/// Percentage for determining if order ships to different customer (gift) +/// Note: In Java, condition is `randomInt > GIFT_PERCENTAGE`, meaning ~92% are gifts +pub const GIFT_PERCENTAGE: i32 = 7; +/// Percentage of orders that get returned +pub const RETURN_PERCENTAGE: i32 = 10; + +/// Order information shared across line items in the same order +struct OrderInfo { + ws_sold_date_sk: i64, + ws_sold_time_sk: i64, + ws_bill_customer_sk: i64, + ws_bill_cdemo_sk: i64, + ws_bill_hdemo_sk: i64, + ws_bill_addr_sk: i64, + ws_ship_customer_sk: i64, + ws_ship_cdemo_sk: i64, + ws_ship_hdemo_sk: i64, + ws_ship_addr_sk: i64, + ws_order_number: i64, +} + +impl OrderInfo { + fn default() -> Self { + OrderInfo { + ws_sold_date_sk: 0, + ws_sold_time_sk: 0, + ws_bill_customer_sk: 0, + ws_bill_cdemo_sk: 0, + ws_bill_hdemo_sk: 0, + ws_bill_addr_sk: 0, + ws_ship_customer_sk: 0, + ws_ship_cdemo_sk: 0, + ws_ship_hdemo_sk: 0, + ws_ship_addr_sk: 0, + ws_order_number: 0, + } + } +} + +pub struct WebSalesRowGenerator { + abstract_generator: AbstractRowGenerator, + item_permutation: Option>, + remaining_line_items: i32, + order_info: OrderInfo, + item_index: i32, + web_returns_generator: WebReturnsRowGenerator, +} + +impl WebSalesRowGenerator { + pub fn new() -> Self { + WebSalesRowGenerator { + abstract_generator: AbstractRowGenerator::new(Table::WebSales), + item_permutation: None, + remaining_line_items: 0, + order_info: OrderInfo::default(), + item_index: 0, + web_returns_generator: WebReturnsRowGenerator::new(), + } + } + + fn generate_order_info(&mut self, row_number: i64, session: &Session) -> Result { + use WebSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + + // Web sales uses generate_join_key for date (not date-based iteration like catalog_sales) + let stream = self + .abstract_generator + .get_random_number_stream(&WsSoldDateSk); + let ws_sold_date_sk = generate_join_key( + &WsSoldDateSk, + stream, + crate::config::Table::DateDim, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsSoldTimeSk); + let ws_sold_time_sk = generate_join_key( + &WsSoldTimeSk, + stream, + crate::config::Table::TimeDim, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsBillCustomerSk); + let ws_bill_customer_sk = generate_join_key( + &WsBillCustomerSk, + stream, + crate::config::Table::Customer, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsBillCdemoSk); + let ws_bill_cdemo_sk = generate_join_key( + &WsBillCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsBillHdemoSk); + let ws_bill_hdemo_sk = generate_join_key( + &WsBillHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 1, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsBillAddrSk); + let ws_bill_addr_sk = generate_join_key( + &WsBillAddrSk, + stream, + crate::config::Table::CustomerAddress, + 1, + scaling, + )?; + + // Usually the billing info and shipping info are the same. + // If it's a "gift", they'll be different. + // Note: Java uses `randomInt > GIFT_PERCENTAGE` which means ~92% get different ship info + let mut ws_ship_customer_sk = ws_bill_customer_sk; + let mut ws_ship_cdemo_sk = ws_bill_cdemo_sk; + let mut ws_ship_hdemo_sk = ws_bill_hdemo_sk; + let mut ws_ship_addr_sk = ws_bill_addr_sk; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipCustomerSk); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + + // Java: if (randomInt > GIFT_PERCENTAGE) + if random_int > GIFT_PERCENTAGE { + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipCustomerSk); + ws_ship_customer_sk = generate_join_key( + &WsShipCustomerSk, + stream, + crate::config::Table::Customer, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipCdemoSk); + ws_ship_cdemo_sk = generate_join_key( + &WsShipCdemoSk, + stream, + crate::config::Table::CustomerDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipHdemoSk); + ws_ship_hdemo_sk = generate_join_key( + &WsShipHdemoSk, + stream, + crate::config::Table::HouseholdDemographics, + 2, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipAddrSk); + ws_ship_addr_sk = generate_join_key( + &WsShipAddrSk, + stream, + crate::config::Table::CustomerAddress, + 2, + scaling, + )?; + } + + let ws_order_number = row_number; + + Ok(OrderInfo { + ws_sold_date_sk, + ws_sold_time_sk, + ws_bill_customer_sk, + ws_bill_cdemo_sk, + ws_bill_hdemo_sk, + ws_bill_addr_sk, + ws_ship_customer_sk, + ws_ship_cdemo_sk, + ws_ship_hdemo_sk, + ws_ship_addr_sk, + ws_order_number, + }) + } + + fn is_last_row_in_order(&self) -> bool { + self.remaining_line_items == 0 + } + + /// Consume remaining seeds for the child (web_returns) generator. + pub fn consume_child_seeds(&mut self) { + self.web_returns_generator.consume_remaining_seeds_for_row(); + } +} + +impl Default for WebSalesRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl RowGenerator for WebSalesRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> Result { + use WebSalesGeneratorColumn::*; + + let scaling = session.get_scaling(); + let item_count = scaling.get_id_count(crate::config::Table::Item) as usize; + + // Initialize item permutation if needed + if self.item_permutation.is_none() { + let stream = self + .abstract_generator + .get_random_number_stream(&WsPermutation); + self.item_permutation = Some(make_permutation(item_count, stream)); + } + + // Start a new order if we've finished the previous one + if self.remaining_line_items == 0 { + self.order_info = self.generate_order_info(row_number, session)?; + + let stream = self.abstract_generator.get_random_number_stream(&WsItemSk); + self.item_index = + RandomValueGenerator::generate_uniform_random_int(1, item_count as i32, stream); + + // WebSales has 8-16 line items per order (vs 4-14 for CatalogSales) + let stream = self + .abstract_generator + .get_random_number_stream(&WsOrderNumber); + self.remaining_line_items = + RandomValueGenerator::generate_uniform_random_int(8, 16, stream); + } + + // Generate null bit map + let stream = self.abstract_generator.get_random_number_stream(&WsNulls); + let null_bit_map = create_null_bit_map(Table::WebSales, stream); + + // Orders are shipped some number of days after they are ordered (1-120 days) + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipDateSk); + let ship_lag = RandomValueGenerator::generate_uniform_random_int(1, 120, stream); + let ws_ship_date_sk = self.order_info.ws_sold_date_sk + ship_lag as i64; + + // Items need to be unique within an order + self.item_index += 1; + if self.item_index > item_count as i32 { + self.item_index = 1; + } + + // Get item from permutation and match surrogate key for SCD + let permutation = self.item_permutation.as_ref().unwrap(); + let item_key = get_permutation_entry(permutation, self.item_index); + let ws_item_sk = match_surrogate_key( + item_key as i64, + self.order_info.ws_sold_date_sk, + crate::config::Table::Item, + scaling, + ); + + // The web page needs to be valid for the sale date + let stream = self + .abstract_generator + .get_random_number_stream(&WsWebPageSk); + let ws_web_page_sk = generate_join_key( + &WsWebPageSk, + stream, + crate::config::Table::WebPage, + self.order_info.ws_sold_date_sk, + scaling, + )?; + + let stream = self + .abstract_generator + .get_random_number_stream(&WsWebSiteSk); + let ws_web_site_sk = generate_join_key( + &WsWebSiteSk, + stream, + crate::config::Table::WebSite, + self.order_info.ws_sold_date_sk, + scaling, + )?; + + // Generate ship mode + let stream = self + .abstract_generator + .get_random_number_stream(&WsShipModeSk); + let ws_ship_mode_sk = generate_join_key( + &WsShipModeSk, + stream, + crate::config::Table::ShipMode, + 1, + scaling, + )?; + + // Generate warehouse + let stream = self + .abstract_generator + .get_random_number_stream(&WsWarehouseSk); + let ws_warehouse_sk = generate_join_key( + &WsWarehouseSk, + stream, + crate::config::Table::Warehouse, + 1, + scaling, + )?; + + // Generate promo sk + let stream = self.abstract_generator.get_random_number_stream(&WsPromoSk); + let ws_promo_sk = generate_join_key( + &WsPromoSk, + stream, + crate::config::Table::Promotion, + 1, + scaling, + )?; + + // Generate pricing + let stream = self.abstract_generator.get_random_number_stream(&WsPricing); + let ws_pricing = generate_pricing_for_sales_table(&get_web_sales_pricing_limits(), stream); + + let web_sales_row = WebSalesRow::new( + null_bit_map, + self.order_info.ws_sold_date_sk, + self.order_info.ws_sold_time_sk, + ws_ship_date_sk, + ws_item_sk, + self.order_info.ws_bill_customer_sk, + self.order_info.ws_bill_cdemo_sk, + self.order_info.ws_bill_hdemo_sk, + self.order_info.ws_bill_addr_sk, + self.order_info.ws_ship_customer_sk, + self.order_info.ws_ship_cdemo_sk, + self.order_info.ws_ship_hdemo_sk, + self.order_info.ws_ship_addr_sk, + ws_web_page_sk, + ws_web_site_sk, + ws_ship_mode_sk, + ws_warehouse_sk, + ws_promo_sk, + self.order_info.ws_order_number, + ws_pricing, + ); + + // Check if this sale gets returned (10% return rate) + // We check and generate the return BEFORE moving the sales row to avoid cloning + let stream = self + .abstract_generator + .get_random_number_stream(&WrIsReturned); + let random_int = RandomValueGenerator::generate_uniform_random_int(0, 99, stream); + + // Generate return row if applicable (using reference before we move sales_row) + let return_row = if random_int < RETURN_PERCENTAGE { + Some( + self.web_returns_generator + .generate_row(session, &web_sales_row)?, + ) + } else { + None + }; + + // Now move (not clone) the sales row into the result + let mut generated_rows: Vec = Vec::with_capacity(2); + generated_rows.push(web_sales_row.into()); + + if let Some(ret_row) = return_row { + generated_rows.push(ret_row); + } + + self.remaining_line_items -= 1; + + Ok(RowGeneratorResult::new_with_multiple( + generated_rows, + self.is_last_row_in_order(), + )) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} diff --git a/tpcdsgen/src/row/web_site_row.rs b/tpcdsgen/src/row/web_site_row.rs new file mode 100644 index 00000000..1bd34537 --- /dev/null +++ b/tpcdsgen/src/row/web_site_row.rs @@ -0,0 +1,328 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::generator::{GeneratorColumn, WebSiteGeneratorColumn}; +use crate::row::TableRow; +use crate::types::{Address, Date, Decimal}; + +#[derive(Debug, Clone, PartialEq)] +pub struct WebSiteRow { + null_bit_map: i64, + web_site_sk: i64, + web_site_id: String, + web_rec_start_date_id: i64, + web_rec_end_date_id: i64, + web_name: String, + web_open_date: i64, + web_close_date: i64, + web_class: String, + web_manager: String, + web_market_id: i32, + web_market_class: String, + web_market_desc: String, + web_market_manager: String, + web_company_id: i32, + web_company_name: String, + web_address: Address, + web_tax_percentage: Decimal, +} + +impl WebSiteRow { + #[allow(clippy::too_many_arguments)] + pub fn new( + null_bit_map: i64, + web_site_sk: i64, + web_site_id: String, + web_rec_start_date_id: i64, + web_rec_end_date_id: i64, + web_name: String, + web_open_date: i64, + web_close_date: i64, + web_class: String, + web_manager: String, + web_market_id: i32, + web_market_class: String, + web_market_desc: String, + web_market_manager: String, + web_company_id: i32, + web_company_name: String, + web_address: Address, + web_tax_percentage: Decimal, + ) -> Self { + WebSiteRow { + null_bit_map, + web_site_sk, + web_site_id, + web_rec_start_date_id, + web_rec_end_date_id, + web_name, + web_open_date, + web_close_date, + web_class, + web_manager, + web_market_id, + web_market_class, + web_market_desc, + web_market_manager, + web_company_id, + web_company_name, + web_address, + web_tax_percentage, + } + } + + // Getters for SCD logic + pub fn web_name(&self) -> &str { + &self.web_name + } + + pub fn web_open_date(&self) -> i64 { + self.web_open_date + } + + pub fn web_close_date(&self) -> i64 { + self.web_close_date + } + + pub fn web_class(&self) -> &str { + &self.web_class + } + + pub fn web_manager(&self) -> &str { + &self.web_manager + } + + pub fn web_market_id(&self) -> i32 { + self.web_market_id + } + + pub fn web_market_class(&self) -> &str { + &self.web_market_class + } + + pub fn web_market_desc(&self) -> &str { + &self.web_market_desc + } + + pub fn web_market_manager(&self) -> &str { + &self.web_market_manager + } + + pub fn web_company_id(&self) -> i32 { + self.web_company_id + } + + pub fn web_company_name(&self) -> &str { + &self.web_company_name + } + + pub fn web_address(&self) -> &Address { + &self.web_address + } + + pub fn web_tax_percentage(&self) -> &Decimal { + &self.web_tax_percentage + } + + fn get_string_or_null_for_key(&self, key: i64, column: WebSiteGeneratorColumn) -> String { + if key == -1 || self.is_null_at(column) { + String::new() + } else { + key.to_string() + } + } + + fn get_string_or_null_string(&self, value: &str, column: WebSiteGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_int(&self, value: i32, column: WebSiteGeneratorColumn) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_string_or_null_decimal( + &self, + value: &Decimal, + column: WebSiteGeneratorColumn, + ) -> String { + if self.is_null_at(column) { + String::new() + } else { + value.to_string() + } + } + + fn get_date_string_or_null_from_julian_days( + &self, + julian_days: i64, + column: WebSiteGeneratorColumn, + ) -> String { + if self.is_null_at(column) || julian_days < 0 { + String::new() + } else { + Date::from_julian_days(julian_days as i32).to_string() + } + } + + fn is_null_at(&self, column: WebSiteGeneratorColumn) -> bool { + let bit_position = column.get_global_column_number() + - WebSiteGeneratorColumn::WebSiteSk.get_global_column_number(); + (self.null_bit_map & (1 << bit_position)) != 0 + } +} + +impl TableRow for WebSiteRow { + fn get_values(&self) -> Vec { + vec![ + self.get_string_or_null_for_key(self.web_site_sk, WebSiteGeneratorColumn::WebSiteSk), + self.get_string_or_null_string(&self.web_site_id, WebSiteGeneratorColumn::WebSiteId), + self.get_date_string_or_null_from_julian_days( + self.web_rec_start_date_id, + WebSiteGeneratorColumn::WebRecStartDateId, + ), + self.get_date_string_or_null_from_julian_days( + self.web_rec_end_date_id, + WebSiteGeneratorColumn::WebRecEndDateId, + ), + self.get_string_or_null_string(&self.web_name, WebSiteGeneratorColumn::WebName), + self.get_string_or_null_for_key( + self.web_open_date, + WebSiteGeneratorColumn::WebOpenDate, + ), + self.get_string_or_null_for_key( + self.web_close_date, + WebSiteGeneratorColumn::WebCloseDate, + ), + self.get_string_or_null_string(&self.web_class, WebSiteGeneratorColumn::WebClass), + self.get_string_or_null_string(&self.web_manager, WebSiteGeneratorColumn::WebManager), + self.get_string_or_null_int(self.web_market_id, WebSiteGeneratorColumn::WebMarketId), + self.get_string_or_null_string( + &self.web_market_class, + WebSiteGeneratorColumn::WebMarketClass, + ), + self.get_string_or_null_string( + &self.web_market_desc, + WebSiteGeneratorColumn::WebMarketDesc, + ), + self.get_string_or_null_string( + &self.web_market_manager, + WebSiteGeneratorColumn::WebMarketManager, + ), + self.get_string_or_null_int(self.web_company_id, WebSiteGeneratorColumn::WebCompanyId), + self.get_string_or_null_string( + &self.web_company_name, + WebSiteGeneratorColumn::WebCompanyName, + ), + self.get_string_or_null_string( + &self.web_address.get_street_number().to_string(), + WebSiteGeneratorColumn::WebAddressStreetNum, + ), + self.get_string_or_null_string( + &self.web_address.get_street_name(), + WebSiteGeneratorColumn::WebAddressStreetName1, + ), + self.get_string_or_null_string( + self.web_address.get_street_type(), + WebSiteGeneratorColumn::WebAddressStreetType, + ), + self.get_string_or_null_string( + self.web_address.get_suite_number(), + WebSiteGeneratorColumn::WebAddressSuiteNum, + ), + self.get_string_or_null_string( + self.web_address.get_city(), + WebSiteGeneratorColumn::WebAddressCity, + ), + self.get_string_or_null_string( + self.web_address.get_county().unwrap_or(""), + WebSiteGeneratorColumn::WebAddressCounty, + ), + self.get_string_or_null_string( + self.web_address.get_state(), + WebSiteGeneratorColumn::WebAddressState, + ), + self.get_string_or_null_string( + &format!("{:05}", self.web_address.get_zip()), + WebSiteGeneratorColumn::WebAddressZip, + ), + self.get_string_or_null_string( + self.web_address.get_country(), + WebSiteGeneratorColumn::WebAddressCountry, + ), + self.get_string_or_null_int( + self.web_address.get_gmt_offset(), + WebSiteGeneratorColumn::WebAddressGmtOffset, + ), + self.get_string_or_null_decimal( + &self.web_tax_percentage, + WebSiteGeneratorColumn::WebTaxPercentage, + ), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_web_site_row_values_count() { + let address = Address::new( + "Suite 1".to_string(), + 100, + "Main St".to_string(), + String::new(), + "Avenue".to_string(), + "Springfield".to_string(), + Some("Sangamon".to_string()), + "IL".to_string(), + "United States".to_string(), + 62701, + -600, + ) + .unwrap(); + + let row = WebSiteRow::new( + 0, + 1, + "AAAAAAAABAAAAAAA".to_string(), + 2450815, + 2451179, + "site_0".to_string(), + 2450820, + -1, + "Unknown".to_string(), + "John Doe".to_string(), + 1, + "Market class".to_string(), + "Market description".to_string(), + "Jane Smith".to_string(), + 1, + "Company A".to_string(), + address, + Decimal::new(650, 2).unwrap(), + ); + + let values = row.get_values(); + assert_eq!(values.len(), 26); + } +} diff --git a/tpcdsgen/src/row/web_site_row_generator.rs b/tpcdsgen/src/row/web_site_row_generator.rs new file mode 100644 index 00000000..80a7f04c --- /dev/null +++ b/tpcdsgen/src/row/web_site_row_generator.rs @@ -0,0 +1,401 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::config::Table as ConfigTable; +use crate::distribution::{FirstNamesWeights, NamesDistributions}; +use crate::generator::WebSiteGeneratorColumn; +use crate::join_key_utils::generate_join_key; +use crate::nulls::create_null_bit_map; +use crate::random::RandomValueGenerator; +use crate::row::{AbstractRowGenerator, RowGenerator, RowGeneratorResult, WebSiteRow}; +use crate::slowly_changing_dimension_utils::{ + compute_scd_key, get_value_for_slowly_changing_dimension, +}; +use crate::table::Table; +use crate::types::{Address, Decimal}; + +pub struct WebSiteRowGenerator { + abstract_generator: AbstractRowGenerator, + previous_row: Option, +} + +impl Default for WebSiteRowGenerator { + fn default() -> Self { + Self::new() + } +} + +impl WebSiteRowGenerator { + pub fn new() -> Self { + Self { + abstract_generator: AbstractRowGenerator::new(Table::WebSite), + previous_row: None, + } + } +} + +impl RowGenerator for WebSiteRowGenerator { + fn generate_row_and_child_rows( + &mut self, + row_number: i64, + session: &crate::config::Session, + _parent_row_generator: Option<&mut dyn RowGenerator>, + _child_row_generator: Option<&mut dyn RowGenerator>, + ) -> crate::error::Result { + let scaling = session.get_scaling(); + + let null_bit_map = create_null_bit_map( + Table::WebSite, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebNulls), + ); + + let web_site_sk = row_number; + let web_class = "Unknown".to_string(); + + let scd_key = compute_scd_key(Table::WebSite, row_number); + let web_site_id = scd_key.get_business_key().to_string(); + let web_rec_start_date_id = scd_key.get_start_date(); + let web_rec_end_date_id = scd_key.get_end_date(); + let is_new_business_key = scd_key.is_new_business_key(); + + // Generate open/close dates and name for new business keys only + let (web_open_date, web_close_date, web_name) = if is_new_business_key { + let open_date = generate_join_key( + &WebSiteGeneratorColumn::WebOpenDate, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebOpenDate), + ConfigTable::DateDim, + row_number, + scaling, + )?; + + let close_date = generate_join_key( + &WebSiteGeneratorColumn::WebCloseDate, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebCloseDate), + ConfigTable::DateDim, + row_number, + scaling, + )?; + + let close_date = if close_date > web_rec_end_date_id { + -1 + } else { + close_date + }; + + let name = format!("site_{}", (row_number / 6)); + + (open_date, close_date, name) + } else { + let prev = self.previous_row.as_ref().unwrap(); + ( + prev.web_open_date(), + prev.web_close_date(), + prev.web_name().to_string(), + ) + }; + + // Field change flags control whether a field changes from one row to the next + let mut field_change_flags = self + .abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebScd) + .next_random() as i32; + + // Generate web_manager + let first_name = NamesDistributions::pick_random_first_name( + if session.is_sexist() { + FirstNamesWeights::MaleFrequency + } else { + FirstNamesWeights::GeneralFrequency + }, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebManager), + )?; + let last_name = NamesDistributions::pick_random_last_name( + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebManager), + )?; + let mut web_manager = format!("{} {}", first_name, last_name); + if let Some(ref prev) = self.previous_row { + web_manager = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_manager().to_string(), + web_manager, + ); + } + field_change_flags >>= 1; + + // Generate web_market_id + let mut web_market_id = RandomValueGenerator::generate_uniform_random_int( + 1, + 6, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebMarketId), + ); + if let Some(ref prev) = self.previous_row { + web_market_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_market_id(), + web_market_id, + ); + } + field_change_flags >>= 1; + + // Generate web_market_class + let mut web_market_class = RandomValueGenerator::generate_random_text( + 20, + 50, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebMarketClass), + ); + if let Some(ref prev) = self.previous_row { + web_market_class = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_market_class().to_string(), + web_market_class, + ); + } + field_change_flags >>= 1; + + // Generate web_market_desc + let mut web_market_desc = RandomValueGenerator::generate_random_text( + 20, + 100, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebMarketDesc), + ); + if let Some(ref prev) = self.previous_row { + web_market_desc = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_market_desc().to_string(), + web_market_desc, + ); + } + field_change_flags >>= 1; + + // Generate web_market_manager + let first_name = NamesDistributions::pick_random_first_name( + if session.is_sexist() { + FirstNamesWeights::MaleFrequency + } else { + FirstNamesWeights::GeneralFrequency + }, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebMarketManager), + )?; + let last_name = NamesDistributions::pick_random_last_name( + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebMarketManager), + )?; + let mut web_market_manager = format!("{} {}", first_name, last_name); + if let Some(ref prev) = self.previous_row { + web_market_manager = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_market_manager().to_string(), + web_market_manager, + ); + } + field_change_flags >>= 1; + + // Generate web_company_id + let mut web_company_id = RandomValueGenerator::generate_uniform_random_int( + 1, + 6, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebCompanyId), + ); + if let Some(ref prev) = self.previous_row { + web_company_id = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_company_id(), + web_company_id, + ); + } + field_change_flags >>= 1; + + // Generate web_company_name + // generate_word doesn't use random numbers - it deterministically creates from seed + // We still need to consume the stream to maintain RNG state + let _company_name_stream = self + .abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebCompanyName); + let mut web_company_name = RandomValueGenerator::generate_word( + web_company_id as i64, + 100, + crate::distribution::get_syllables_distribution(), + ); + if let Some(ref prev) = self.previous_row { + web_company_name = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_company_name().to_string(), + web_company_name, + ); + } + field_change_flags >>= 1; + + // Generate address + let mut web_address = Address::make_address_for_column( + Table::WebSite, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebAddress), + scaling, + )?; + + // Some address fields always use new value due to bug in C code, but we still update flags + field_change_flags >>= 1; // city + field_change_flags >>= 1; // county + + // gmt_offset + let mut gmt_offset = web_address.get_gmt_offset(); + if let Some(ref prev) = self.previous_row { + gmt_offset = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_address().get_gmt_offset(), + gmt_offset, + ); + } + field_change_flags >>= 1; + + field_change_flags >>= 1; // state + field_change_flags >>= 1; // streetType + field_change_flags >>= 1; // streetName1 + field_change_flags >>= 1; // streetName2 + + // street_number + let mut street_number = web_address.get_street_number(); + if let Some(ref prev) = self.previous_row { + street_number = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_address().get_street_number(), + street_number, + ); + } + field_change_flags >>= 1; + + // zip + let mut zip = web_address.get_zip(); + if let Some(ref prev) = self.previous_row { + zip = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + prev.web_address().get_zip(), + zip, + ); + } + field_change_flags >>= 1; + + // Reconstruct address with potentially changed fields + web_address = Address::new( + web_address.get_suite_number().to_string(), + street_number, + web_address.get_street_name1().to_string(), + web_address.get_street_name2().to_string(), + web_address.get_street_type().to_string(), + web_address.get_city().to_string(), + web_address.get_county().map(|s| s.to_string()), + web_address.get_state().to_string(), + web_address.get_country().to_string(), + zip, + gmt_offset, + )?; + + // Generate web_tax_percentage + let mut web_tax_percentage = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::ZERO, + Decimal::new(12, 2)?, + self.abstract_generator + .get_random_number_stream(&WebSiteGeneratorColumn::WebTaxPercentage), + ); + if let Some(ref prev) = self.previous_row { + web_tax_percentage = get_value_for_slowly_changing_dimension( + field_change_flags, + is_new_business_key, + *prev.web_tax_percentage(), + web_tax_percentage, + ); + } + + let row = WebSiteRow::new( + null_bit_map, + web_site_sk, + web_site_id, + web_rec_start_date_id, + web_rec_end_date_id, + web_name, + web_open_date, + web_close_date, + web_class, + web_manager, + web_market_id, + web_market_class, + web_market_desc, + web_market_manager, + web_company_id, + web_company_name, + web_address, + web_tax_percentage, + ); + + self.previous_row = Some(row.clone()); + Ok(RowGeneratorResult::new(row)) + } + + fn consume_remaining_seeds_for_row(&mut self) { + self.abstract_generator.consume_remaining_seeds_for_row(); + } + + fn skip_rows_until_starting_row_number(&mut self, starting_row_number: i64) { + self.abstract_generator + .skip_rows_until_starting_row_number(starting_row_number); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::row::TableRow; + + #[test] + fn test_web_site_row_generator_creation() { + let _generator = WebSiteRowGenerator::new(); + // Should not panic + assert!(true); + } + + #[test] + fn test_generate_web_site_row() { + use crate::config::Session; + + let mut generator = WebSiteRowGenerator::new(); + let session = Session::get_default_session(); + + let result = generator.generate_row_and_child_rows(1, &session, None, None); + assert!(result.is_ok()); + + let row_result = result.unwrap(); + let values = row_result.get_rows()[0].get_values(); + assert_eq!(values.len(), 26); + } +} diff --git a/tpcdsgen/src/scaling_info.rs b/tpcdsgen/src/scaling_info.rs new file mode 100644 index 00000000..74bd5413 --- /dev/null +++ b/tpcdsgen/src/scaling_info.rs @@ -0,0 +1,313 @@ +use crate::{check_argument, error::Result, TpcdsError}; +use std::collections::HashMap; + +/// Scaling models for table row count calculation (ScalingInfo.ScalingModel) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScalingModel { + Static, + Linear, + Logarithmic, +} + +/// Scaling information for table row count calculation (ScalingInfo) +#[derive(Debug, Clone)] +pub struct ScalingInfo { + /// Multiplier for calculations + multiplier: i32, + /// Scaling model to use + scaling_model: ScalingModel, + /// Map from scale factors to row counts + scales_to_row_counts_map: HashMap, // Using i32 for scale keys for simpler lookup + /// Update percentage + update_percentage: i32, +} + +impl ScalingInfo { + /// Defined scale factors (DEFINED_SCALES) + pub const DEFINED_SCALES: [f64; 10] = [ + 0.0, 1.0, 10.0, 100.0, 300.0, 1000.0, 3000.0, 10000.0, 30000.0, 100000.0, + ]; + + /// Create new ScalingInfo + pub fn new( + multiplier: i32, + scaling_model: ScalingModel, + row_counts_per_scale: &[i32], + update_percentage: i32, + ) -> Result { + check_argument!( + multiplier >= 0, + "multiplier is not greater than or equal to 0" + ); + check_argument!( + update_percentage >= 0, + "updatePercentage is not greater than or equal to zero" + ); + check_argument!( + row_counts_per_scale.len() == Self::DEFINED_SCALES.len(), + "row_counts_per_scale length must match DEFINED_SCALES length" + ); + + let mut scales_to_row_counts_map = HashMap::new(); + for (i, &row_count) in row_counts_per_scale.iter().enumerate() { + check_argument!(row_count >= 0, "row counts cannot be negative"); + // Convert float scale to int key for HashMap (multiply by 1000 to preserve precision) + let scale_key = (Self::DEFINED_SCALES[i] * 1000.0) as i32; + scales_to_row_counts_map.insert(scale_key, row_count); + } + + Ok(ScalingInfo { + multiplier, + scaling_model, + scales_to_row_counts_map, + update_percentage, + }) + } + + /// Get the multiplier + pub fn get_multiplier(&self) -> i32 { + self.multiplier + } + + /// Get the scaling model + pub fn get_scaling_model(&self) -> ScalingModel { + self.scaling_model + } + + /// Get the update percentage + pub fn get_update_percentage(&self) -> i32 { + self.update_percentage + } + + /// Get row count for a given scale (getRowCountForScale) + pub fn get_row_count_for_scale(&self, scale: f64) -> Result { + check_argument!(scale <= 100000.0, "scale must be less than 100000"); + + let scale_key = (scale * 1000.0) as i32; + if let Some(&row_count) = self.scales_to_row_counts_map.get(&scale_key) { + return Ok(row_count as i64); + } + + // Get the scaling model for the table + match self.scaling_model { + ScalingModel::Static => self.compute_count_using_static_scale(), + ScalingModel::Linear => self.compute_count_using_linear_scale(scale), + ScalingModel::Logarithmic => self.compute_count_using_log_scale(scale), + } + } + + /// Compute count using static scale model + fn compute_count_using_static_scale(&self) -> Result { + self.get_row_count_for_scale(1.0) + } + + /// Compute count using logarithmic scale model (computeCountUsingLogScale) + fn compute_count_using_log_scale(&self, scale: f64) -> Result { + let scale_slot = Self::get_scale_slot(scale)?; + let delta = self.get_row_count_for_scale(Self::DEFINED_SCALES[scale_slot])? + - self.get_row_count_for_scale(Self::DEFINED_SCALES[scale_slot - 1])?; + + let float_offset = (scale - Self::DEFINED_SCALES[scale_slot - 1]) + / (Self::DEFINED_SCALES[scale_slot] - Self::DEFINED_SCALES[scale_slot - 1]); + + let base_row_count = if scale < 1.0 { + self.get_row_count_for_scale(Self::DEFINED_SCALES[0])? + } else { + self.get_row_count_for_scale(Self::DEFINED_SCALES[1])? + }; + + let count = ((float_offset * delta as f64) as i64) + base_row_count; + Ok(if count == 0 { 1 } else { count }) + } + + /// Get scale slot for a given scale (getScaleSlot) + fn get_scale_slot(scale: f64) -> Result { + for (i, &defined_scale) in Self::DEFINED_SCALES.iter().enumerate() { + if scale <= defined_scale { + return Ok(i); + } + } + + // Shouldn't be able to get here because we checked the scale argument + Err(TpcdsError::new("scale was greater than max scale")) + } + + /// Compute count using linear scale model (computeCountUsingLinearScale) + fn compute_count_using_linear_scale(&self, scale: f64) -> Result { + let mut row_count = 0i64; + let mut target_gb = scale; + + if scale < 1.0 { + let base_count = self.get_row_count_for_scale(Self::DEFINED_SCALES[1])?; + row_count = (scale * base_count as f64).round() as i64; + return Ok(if row_count == 0 { 1 } else { row_count }); + } + + // Work from large scales down + for i in (1..Self::DEFINED_SCALES.len()).rev() { + // Use the defined rowcounts to build up the target GB volume + while target_gb >= Self::DEFINED_SCALES[i] { + row_count += self.get_row_count_for_scale(Self::DEFINED_SCALES[i])?; + target_gb -= Self::DEFINED_SCALES[i]; + } + } + + Ok(row_count) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scaling_info_creation() { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + let scaling_info = ScalingInfo::new(3, ScalingModel::Logarithmic, &row_counts, 0).unwrap(); + + assert_eq!(scaling_info.get_multiplier(), 3); + assert_eq!(scaling_info.get_scaling_model(), ScalingModel::Logarithmic); + assert_eq!(scaling_info.get_update_percentage(), 0); + } + + #[test] + fn test_scaling_info_validation() { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + + // Test negative multiplier + assert!(ScalingInfo::new(-1, ScalingModel::Static, &row_counts, 0).is_err()); + + // Test negative update percentage + assert!(ScalingInfo::new(0, ScalingModel::Static, &row_counts, -1).is_err()); + + // Test wrong array length + let wrong_counts = [0, 100, 500]; + assert!(ScalingInfo::new(0, ScalingModel::Static, &wrong_counts, 0).is_err()); + + // Test negative row count + let negative_counts = [0, -100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + assert!(ScalingInfo::new(0, ScalingModel::Static, &negative_counts, 0).is_err()); + } + + #[test] + fn test_static_scaling() { + let row_counts = [ + 0, 73049, 73049, 73049, 73049, 73049, 73049, 73049, 73049, 73049, + ]; + let scaling_info = ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0).unwrap(); + + // Static scaling should always return the scale=1 value + assert_eq!(scaling_info.get_row_count_for_scale(1.0).unwrap(), 73049); + assert_eq!(scaling_info.get_row_count_for_scale(10.0).unwrap(), 73049); + assert_eq!(scaling_info.get_row_count_for_scale(1000.0).unwrap(), 73049); + } + + #[test] + fn test_exact_scale_matches() { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + let scaling_info = ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0).unwrap(); + + // Test exact matches from the defined scales + assert_eq!(scaling_info.get_row_count_for_scale(0.0).unwrap(), 0); + assert_eq!(scaling_info.get_row_count_for_scale(1.0).unwrap(), 100); + assert_eq!(scaling_info.get_row_count_for_scale(10.0).unwrap(), 500); + assert_eq!(scaling_info.get_row_count_for_scale(100.0).unwrap(), 2000); + assert_eq!(scaling_info.get_row_count_for_scale(1000.0).unwrap(), 12000); + } + + #[test] + fn test_logarithmic_scaling_interpolation() { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + let scaling_info = ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0).unwrap(); + + // Test interpolation - should be between defined points + let result_5 = scaling_info.get_row_count_for_scale(5.0).unwrap(); + assert!(result_5 > 100); // Greater than scale=1 result + assert!(result_5 < 500); // Less than scale=10 result + } + + #[test] + fn test_linear_scaling_fractional() { + let row_counts = [ + 0, 24, 240, 2400, 7200, 24000, 72000, 240000, 720000, 2400000, + ]; + let scaling_info = ScalingInfo::new(4, ScalingModel::Linear, &row_counts, 0).unwrap(); + + // Test fractional scaling (< 1.0) + let result = scaling_info.get_row_count_for_scale(0.5).unwrap(); + assert_eq!(result, 12); // 0.5 * 24 = 12 + + // Test that zero result becomes 1 + let result_tiny = scaling_info.get_row_count_for_scale(0.001).unwrap(); + assert_eq!(result_tiny, 1); + } + + #[test] + fn test_linear_scaling_large() { + let row_counts = [ + 0, 24, 240, 2400, 7200, 24000, 72000, 240000, 720000, 2400000, + ]; + let scaling_info = ScalingInfo::new(4, ScalingModel::Linear, &row_counts, 0).unwrap(); + + // Test larger scale that requires multiple additions + let result = scaling_info.get_row_count_for_scale(1100.0).unwrap(); + // Linear scaling works from largest to smallest: + // 1100.0 - 1000.0 = 100.0 remaining, use scale[1000] = 24000 + // 100.0 - 100.0 = 0.0 remaining, use scale[100] = 2400 + // Total: 24000 + 2400 = 26400 + assert_eq!(result, 26400); + } + + #[test] + fn test_scale_validation() { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + let scaling_info = ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0).unwrap(); + + // Test scale too large + assert!(scaling_info.get_row_count_for_scale(100001.0).is_err()); + } + + #[test] + fn test_get_scale_slot() { + assert_eq!(ScalingInfo::get_scale_slot(0.0).unwrap(), 0); + assert_eq!(ScalingInfo::get_scale_slot(0.5).unwrap(), 1); + assert_eq!(ScalingInfo::get_scale_slot(1.0).unwrap(), 1); + assert_eq!(ScalingInfo::get_scale_slot(5.0).unwrap(), 2); + assert_eq!(ScalingInfo::get_scale_slot(10.0).unwrap(), 2); + assert_eq!(ScalingInfo::get_scale_slot(50.0).unwrap(), 3); + assert_eq!(ScalingInfo::get_scale_slot(100000.0).unwrap(), 9); + + // Test scale too large + assert!(ScalingInfo::get_scale_slot(100001.0).is_err()); + } + + #[test] + fn test_defined_scales_constant() { + let expected = [ + 0.0, 1.0, 10.0, 100.0, 300.0, 1000.0, 3000.0, 10000.0, 30000.0, 100000.0, + ]; + assert_eq!(ScalingInfo::DEFINED_SCALES, expected); + } + + #[test] + fn test_scaling_models() { + assert_eq!(format!("{:?}", ScalingModel::Static), "Static"); + assert_eq!(format!("{:?}", ScalingModel::Linear), "Linear"); + assert_eq!(format!("{:?}", ScalingModel::Logarithmic), "Logarithmic"); + } + + #[test] + fn test_java_call_center_example() { + // Test with actual CallCenter values from Java Table.java + let row_counts = [0, 3, 12, 15, 18, 21, 24, 27, 30, 30]; + let scaling_info = ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0).unwrap(); + + // Test some specific scale calculations + assert_eq!(scaling_info.get_row_count_for_scale(1.0).unwrap(), 3); + assert_eq!(scaling_info.get_row_count_for_scale(100000.0).unwrap(), 30); + + // Test interpolation + let result_5 = scaling_info.get_row_count_for_scale(5.0).unwrap(); + assert!(result_5 >= 3 && result_5 <= 12); + } +} diff --git a/tpcdsgen/src/slowly_changing_dimension_utils.rs b/tpcdsgen/src/slowly_changing_dimension_utils.rs new file mode 100644 index 00000000..cd0cf3e1 --- /dev/null +++ b/tpcdsgen/src/slowly_changing_dimension_utils.rs @@ -0,0 +1,264 @@ +use crate::business_key_generator::make_business_key; +use crate::table::Table; +use crate::types::Date; + +const ONE_HALF_DATE: i64 = + Date::JULIAN_DATA_START_DATE + (Date::JULIAN_DATA_END_DATE - Date::JULIAN_DATA_START_DATE) / 2; +const ONE_THIRD_PERIOD: i64 = (Date::JULIAN_DATA_END_DATE - Date::JULIAN_DATA_START_DATE) / 3; +const ONE_THIRD_DATE: i64 = Date::JULIAN_DATA_START_DATE + ONE_THIRD_PERIOD; +const TWO_THIRDS_DATE: i64 = ONE_THIRD_DATE + ONE_THIRD_PERIOD; + +#[derive(Debug, Clone)] +pub struct SlowlyChangingDimensionKey { + business_key: String, + start_date: i64, + end_date: i64, + is_new_business_key: bool, +} + +impl SlowlyChangingDimensionKey { + pub fn new( + business_key: String, + start_date: i64, + end_date: i64, + is_new_business_key: bool, + ) -> Self { + Self { + business_key, + start_date, + end_date, + is_new_business_key, + } + } + + pub fn get_business_key(&self) -> &str { + &self.business_key + } + + pub fn get_start_date(&self) -> i64 { + self.start_date + } + + pub fn get_end_date(&self) -> i64 { + self.end_date + } + + pub fn is_new_business_key(&self) -> bool { + self.is_new_business_key + } +} + +pub fn compute_scd_key(table: Table, row_number: i64) -> SlowlyChangingDimensionKey { + let modulo = (row_number % 6) as i32; + let table_number = table.get_ordinal(); // Use Java ordinal, not Rust enum discriminant + + let (business_key, start_date, mut end_date, is_new_key) = match modulo { + 1 => { + // 1 revision + let business_key = make_business_key(row_number); + let start_date = Date::JULIAN_DATA_START_DATE - table_number * 6; + let end_date = -1; + (business_key, start_date, end_date, true) + } + 2 => { + // 1 of 2 revisions + let business_key = make_business_key(row_number); + let start_date = Date::JULIAN_DATA_START_DATE - table_number * 6; + let end_date = ONE_HALF_DATE - table_number * 6; + (business_key, start_date, end_date, true) + } + 3 => { + // 2 of 2 revisions + let business_key = make_business_key(row_number - 1); + let start_date = ONE_HALF_DATE - table_number * 6 + 1; + let end_date = -1; + (business_key, start_date, end_date, false) + } + 4 => { + // 1 of 3 revisions + let business_key = make_business_key(row_number); + let start_date = Date::JULIAN_DATA_START_DATE - table_number * 6; + let end_date = ONE_THIRD_DATE - table_number * 6; + (business_key, start_date, end_date, true) + } + 5 => { + // 2 of 3 revisions + let business_key = make_business_key(row_number - 1); + let start_date = ONE_THIRD_DATE - table_number * 6 + 1; + let end_date = TWO_THIRDS_DATE - table_number * 6; + (business_key, start_date, end_date, false) + } + 0 => { + // 3 of 3 revisions + let business_key = make_business_key(row_number - 2); + let start_date = TWO_THIRDS_DATE - table_number * 6 + 1; + let end_date = -1; + (business_key, start_date, end_date, false) + } + _ => panic!( + "Something's wrong. Positive integers % 6 should always be covered by one of the cases" + ), + }; + + if end_date > Date::JULIAN_DATA_END_DATE { + end_date = -1; + } + + SlowlyChangingDimensionKey::new(business_key, start_date, end_date, is_new_key) +} + +pub fn get_value_for_slowly_changing_dimension( + field_change_flag: i32, + is_new_key: bool, + old_value: T, + new_value: T, +) -> T { + if should_change_dimension(field_change_flag, is_new_key) { + new_value + } else { + old_value + } +} + +pub fn should_change_dimension(flags: i32, is_new_key: bool) -> bool { + flags % 2 == 0 || is_new_key +} + +/// Match surrogate key for SCD tables based on unique ID and julian date. +/// +/// This converts a unique ID (which represents a business key) into the appropriate +/// surrogate key (row number) based on the date, accounting for SCD history revisions. +/// +/// # Arguments +/// * `unique` - The unique business key ID (1-based) +/// * `julian_date` - The julian date for temporal matching +/// * `table` - The SCD table being referenced (config::Table) +/// * `scaling` - Scaling information for the table +/// +/// # Returns +/// The surrogate key (row number) that matches the business key at the given date +pub fn match_surrogate_key( + unique: i64, + julian_date: i64, + table: crate::config::Table, + scaling: &crate::config::Scaling, +) -> i64 { + let mut surrogate_key = (unique / 3) * 6; + + match unique % 3 { + 1 => { + // Only one occurrence of this ID + surrogate_key += 1; + } + 2 => { + // Two revisions of this ID + surrogate_key += 2; + if julian_date > ONE_HALF_DATE { + surrogate_key += 1; + } + } + 0 => { + // Three revisions of this ID + surrogate_key -= 2; + if julian_date > ONE_THIRD_DATE { + surrogate_key += 1; + } + if julian_date > TWO_THIRDS_DATE { + surrogate_key += 1; + } + } + _ => panic!("unique % 3 did not equal 0, 1, or 2"), + } + + let row_count = scaling.get_row_count(table); + if surrogate_key > row_count { + surrogate_key = row_count; + } + + surrogate_key +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::Scaling; + + #[test] + fn test_should_change_dimension() { + assert!(should_change_dimension(0, false)); // even flag + assert!(!should_change_dimension(1, false)); // odd flag + assert!(should_change_dimension(1, true)); // new key overrides odd flag + assert!(should_change_dimension(0, true)); // new key + even flag + } + + #[test] + fn test_match_surrogate_key_single_revision() { + let scaling = Scaling::new(1.0); + // unique % 3 == 1 means single revision + let surrogate = match_surrogate_key( + 1, + Date::JULIAN_DATA_START_DATE, + crate::config::Table::Item, + &scaling, + ); + assert_eq!(surrogate, 1); // (1/3)*6 + 1 = 0 + 1 = 1 + } + + #[test] + fn test_match_surrogate_key_two_revisions() { + let scaling = Scaling::new(1.0); + // unique % 3 == 2 means two revisions + // Before half date: surrogate_key = (unique/3)*6 + 2 + let surrogate = match_surrogate_key( + 2, + Date::JULIAN_DATA_START_DATE, + crate::config::Table::Item, + &scaling, + ); + assert_eq!(surrogate, 2); // (2/3)*6 + 2 = 0 + 2 = 2 + + // After half date: surrogate_key = (unique/3)*6 + 2 + 1 + let surrogate = + match_surrogate_key(2, ONE_HALF_DATE + 1, crate::config::Table::Item, &scaling); + assert_eq!(surrogate, 3); // (2/3)*6 + 2 + 1 = 0 + 3 = 3 + } + + #[test] + fn test_match_surrogate_key_three_revisions() { + let scaling = Scaling::new(1.0); + // unique % 3 == 0 means three revisions + // Before one-third: (unique/3)*6 - 2 + let surrogate = match_surrogate_key( + 3, + Date::JULIAN_DATA_START_DATE, + crate::config::Table::Item, + &scaling, + ); + assert_eq!(surrogate, 4); // (3/3)*6 - 2 = 6 - 2 = 4 + + // Between one-third and two-thirds: (unique/3)*6 - 2 + 1 + let surrogate = + match_surrogate_key(3, ONE_THIRD_DATE + 1, crate::config::Table::Item, &scaling); + assert_eq!(surrogate, 5); // (3/3)*6 - 2 + 1 = 5 + + // After two-thirds: (unique/3)*6 - 2 + 1 + 1 + let surrogate = + match_surrogate_key(3, TWO_THIRDS_DATE + 1, crate::config::Table::Item, &scaling); + assert_eq!(surrogate, 6); // (3/3)*6 - 2 + 2 = 6 + } + + #[test] + fn test_match_surrogate_key_capped_at_row_count() { + let scaling = Scaling::new(1.0); + // For a very large unique ID, surrogate should be capped at row count + let row_count = scaling.get_row_count(crate::config::Table::Item); + let large_unique = 100000; + let surrogate = match_surrogate_key( + large_unique, + Date::JULIAN_DATA_START_DATE, + crate::config::Table::Item, + &scaling, + ); + assert_eq!(surrogate, row_count); + } +} diff --git a/tpcdsgen/src/table.rs b/tpcdsgen/src/table.rs new file mode 100644 index 00000000..e40a4642 --- /dev/null +++ b/tpcdsgen/src/table.rs @@ -0,0 +1,996 @@ +use crate::column::{ + CallCenterColumn, Column, CustomerAddressColumn, CustomerColumn, DbgenVersionColumn, + HouseholdDemographicsColumn, InventoryColumn, PromotionColumn, WebSiteColumn, +}; +use crate::error::Result; +use crate::generator::{ + CallCenterGeneratorColumn, CatalogPageGeneratorColumn, CatalogReturnsGeneratorColumn, + CatalogSalesGeneratorColumn, CustomerAddressGeneratorColumn, + CustomerDemographicsGeneratorColumn, CustomerGeneratorColumn, DateDimGeneratorColumn, + DbgenVersionGeneratorColumn, GeneratorColumn, HouseholdDemographicsGeneratorColumn, + IncomeBandGeneratorColumn, InventoryGeneratorColumn, ItemGeneratorColumn, + PromotionGeneratorColumn, ReasonGeneratorColumn, ShipModeGeneratorColumn, StoreGeneratorColumn, + StoreReturnsGeneratorColumn, StoreSalesGeneratorColumn, TimeDimGeneratorColumn, + WarehouseGeneratorColumn, WebPageGeneratorColumn, WebReturnsGeneratorColumn, + WebSalesGeneratorColumn, WebSiteGeneratorColumn, +}; +use crate::scaling_info::{ScalingInfo, ScalingModel}; +use crate::table_flags::TableFlags; +use std::sync::OnceLock; + +/// Table enum representing all TPC-DS tables with complete metadata (Table) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Table { + CallCenter, + CatalogPage, + CatalogReturns, + CatalogSales, + Warehouse, + ShipMode, + Reason, + IncomeBand, + HouseholdDemographics, + CustomerDemographics, + CustomerAddress, + Customer, + DateDim, + TimeDim, + Item, + Promotion, + Store, + StoreReturns, + StoreSales, + WebPage, + WebReturns, + WebSales, + WebSite, + DbgenVersion, + Inventory, + // Source tables (for SCD key computation) + SStore, +} + +impl Table { + /// Get the lowercase table name (getName()) + pub fn get_name(&self) -> &'static str { + match self { + Table::CallCenter => "call_center", + Table::CatalogPage => "catalog_page", + Table::CatalogReturns => "catalog_returns", + Table::CatalogSales => "catalog_sales", + Table::Warehouse => "warehouse", + Table::ShipMode => "ship_mode", + Table::Reason => "reason", + Table::IncomeBand => "income_band", + Table::HouseholdDemographics => "household_demographics", + Table::CustomerDemographics => "customer_demographics", + Table::CustomerAddress => "customer_address", + Table::Customer => "customer", + Table::DateDim => "date_dim", + Table::TimeDim => "time_dim", + Table::Item => "item", + Table::Promotion => "promotion", + Table::Store => "store", + Table::StoreReturns => "store_returns", + Table::StoreSales => "store_sales", + Table::WebPage => "web_page", + Table::WebReturns => "web_returns", + Table::WebSales => "web_sales", + Table::WebSite => "web_site", + Table::DbgenVersion => "dbgen_version", + Table::Inventory => "inventory", + Table::SStore => "s_store", + } + } + + /// Get the Java Table enum ordinal + /// This MUST match the exact order in Java's Table.java enum declaration + /// Used for SCD date calculations (table_number * 6 offset) + pub fn get_ordinal(&self) -> i64 { + match self { + // Java enum order (from Table.java): + Table::CallCenter => 0, + Table::CatalogPage => 1, + Table::CatalogReturns => 2, + Table::CatalogSales => 3, + Table::Customer => 4, + Table::CustomerAddress => 5, + Table::CustomerDemographics => 6, + Table::DateDim => 7, + Table::HouseholdDemographics => 8, + Table::IncomeBand => 9, + Table::Inventory => 10, + Table::Item => 11, + Table::Promotion => 12, + Table::Reason => 13, + Table::ShipMode => 14, + Table::Store => 15, + Table::StoreReturns => 16, + Table::StoreSales => 17, + Table::TimeDim => 18, + Table::Warehouse => 19, + Table::WebPage => 20, + Table::WebReturns => 21, + Table::WebSales => 22, + Table::WebSite => 23, + Table::DbgenVersion => 24, + // Source tables (after the 25 base tables) + Table::SStore => 49, + } + } + + /// Get table flags using const static array for efficiency. + /// Format: TableFlags::new(keeps_history, is_small, is_date_based) + pub fn get_table_flags(&self) -> &'static TableFlags { + // Const static array indexed by enum variant order (not Java ordinal) + // Order matches enum definition: CallCenter, CatalogPage, ..., SStore + static TABLE_FLAGS: [TableFlags; 26] = [ + TableFlags::new(true, true, false), // CallCenter: keeps_history, is_small + TableFlags::new(false, false, false), // CatalogPage: default + TableFlags::new(false, false, false), // CatalogReturns: default + TableFlags::new(false, false, true), // CatalogSales: is_date_based + TableFlags::new(false, true, false), // Warehouse: is_small + TableFlags::new(false, true, false), // ShipMode: is_small + TableFlags::new(false, true, false), // Reason: is_small + TableFlags::new(false, true, false), // IncomeBand: is_small + TableFlags::new(false, false, false), // HouseholdDemographics: default + TableFlags::new(false, false, false), // CustomerDemographics: default + TableFlags::new(false, false, false), // CustomerAddress: default + TableFlags::new(false, false, false), // Customer: default + TableFlags::new(false, false, false), // DateDim: default + TableFlags::new(false, false, false), // TimeDim: default + TableFlags::new(true, false, false), // Item: keeps_history + TableFlags::new(false, false, false), // Promotion: default + TableFlags::new(true, true, false), // Store: keeps_history, is_small + TableFlags::new(false, false, false), // StoreReturns: default + TableFlags::new(false, false, true), // StoreSales: is_date_based + TableFlags::new(true, false, false), // WebPage: keeps_history + TableFlags::new(false, false, false), // WebReturns: default + TableFlags::new(false, false, true), // WebSales: is_date_based + TableFlags::new(true, true, false), // WebSite: keeps_history, is_small + TableFlags::new(false, false, false), // DbgenVersion: default + TableFlags::new(false, false, true), // Inventory: is_date_based + TableFlags::new(false, false, false), // SStore: default + ]; + + &TABLE_FLAGS[*self as usize] + } + + /// Get null basis points for this table + pub fn get_null_basis_points(&self) -> i32 { + // Const array indexed by enum variant order + const NULL_BASIS_POINTS: [i32; 26] = [ + 100, // CallCenter + 200, // CatalogPage + 400, // CatalogReturns + 100, // CatalogSales + 100, // Warehouse + 100, // ShipMode + 100, // Reason + 0, // IncomeBand + 0, // HouseholdDemographics + 0, // CustomerDemographics + 600, // CustomerAddress + 700, // Customer + 0, // DateDim + 0, // TimeDim + 50, // Item + 200, // Promotion + 100, // Store + 700, // StoreReturns + 900, // StoreSales + 250, // WebPage + 900, // WebReturns + 5, // WebSales + 100, // WebSite + 0, // DbgenVersion + 1000, // Inventory + 0, // SStore + ]; + NULL_BASIS_POINTS[*self as usize] + } + + /// Get not-null bitmap for this table + pub fn get_not_null_bit_map(&self) -> i64 { + // Const array indexed by enum variant order + const NOT_NULL_BIT_MAP: [i64; 26] = [ + 0xB, // CallCenter + 0x3, // CatalogPage + 0x10007, // CatalogReturns + 0x28000, // CatalogSales + 0x3, // Warehouse + 0x3, // ShipMode + 0x3, // Reason + 0x1, // IncomeBand + 0x1, // HouseholdDemographics + 0x1, // CustomerDemographics + 0x3, // CustomerAddress + 0x13, // Customer + 0x3, // DateDim + 0x3, // TimeDim + 0xB, // Item + 0x3, // Promotion + 0xB, // Store + 0x204, // StoreReturns + 0x204, // StoreSales + 0xB, // WebPage + 0x2004, // WebReturns + 0x20008, // WebSales + 0xB, // WebSite + 0x0, // DbgenVersion + 0x07, // Inventory + 0x0, // SStore + ]; + NOT_NULL_BIT_MAP[*self as usize] + } + + /// Get scaling info for this table + pub fn get_scaling_info(&self) -> &'static ScalingInfo { + match self { + Table::CallCenter => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 3, 12, 15, 18, 21, 24, 27, 30, 30]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("CallCenter ScalingInfo creation should not fail") + }) + } + Table::CatalogPage => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 11718, 12000, 20400, 26000, 30000, 36000, 40000, 46000, 50000, + ]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("CatalogPage ScalingInfo creation should not fail") + }) + } + Table::CatalogReturns => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 16, 160, 1600, 4800, 16000, 48000, 160000, 480000, 1600000, + ]; + ScalingInfo::new(4, ScalingModel::Linear, &row_counts, 0) + .expect("CatalogReturns ScalingInfo creation should not fail") + }) + } + Table::CatalogSales => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 16, 160, 1600, 4800, 16000, 48000, 160000, 480000, 1600000, + ]; + ScalingInfo::new(4, ScalingModel::Linear, &row_counts, 0) + .expect("CatalogSales ScalingInfo creation should not fail") + }) + } + Table::Warehouse => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + // Java: new ScalingInfo(0, LOGARITHMIC, new int[] {0, 5, 10, 15, 17, 20, 22, 25, 27, 30}, 0) + let row_counts = [0, 5, 10, 15, 17, 20, 22, 25, 27, 30]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Warehouse ScalingInfo creation should not fail") + }) + } + Table::ShipMode => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + // Java: new ScalingInfo(0, STATIC, new int[] {0, 20, 20, 20, 20, 20, 20, 20, 20, 20}, 0) + let row_counts = [0, 20, 20, 20, 20, 20, 20, 20, 20, 20]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("ShipMode ScalingInfo creation should not fail") + }) + } + Table::Reason => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + // Java: new ScalingInfo(0, LOGARITHMIC, new int[] {0, 35, 45, 55, 60, 65, 67, 70, 72, 75}, 0) + let row_counts = [0, 35, 45, 55, 60, 65, 67, 70, 72, 75]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Reason ScalingInfo creation should not fail") + }) + } + Table::IncomeBand => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + // Java: new ScalingInfo(0, STATIC, new int[] {0, 20, 20, 20, 20, 20, 20, 20, 20, 20}, 0) + let row_counts = [0, 20, 20, 20, 20, 20, 20, 20, 20, 20]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("IncomeBand ScalingInfo creation should not fail") + }) + } + Table::HouseholdDemographics => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200, 7200]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("HouseholdDemographics ScalingInfo creation should not fail") + }) + } + Table::CustomerDemographics => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 19208, 19208, 19208, 19208, 19208, 19208, 19208, 19208, 19208, + ]; + ScalingInfo::new(2, ScalingModel::Static, &row_counts, 0) + .expect("CustomerDemographics ScalingInfo creation should not fail") + }) + } + Table::CustomerAddress => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 50, 250, 1000, 2500, 6000, 15000, 32500, 40000, 50000]; + ScalingInfo::new(3, ScalingModel::Logarithmic, &row_counts, 0) + .expect("CustomerAddress ScalingInfo creation should not fail") + }) + } + Table::Customer => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 100, 500, 2000, 5000, 12000, 30000, 65000, 80000, 100000]; + ScalingInfo::new(3, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Customer ScalingInfo creation should not fail") + }) + } + Table::DateDim => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 73049, 73049, 73049, 73049, 73049, 73049, 73049, 73049, 73049, + ]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("DateDim ScalingInfo creation should not fail") + }) + } + Table::TimeDim => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, 86400, + ]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("TimeDim ScalingInfo creation should not fail") + }) + } + Table::Item => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 9, 51, 102, 132, 150, 180, 201, 231, 251]; + ScalingInfo::new(3, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Item ScalingInfo creation should not fail") + }) + } + Table::Promotion => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 300, 500, 1000, 1300, 1500, 1800, 2000, 2300, 2500]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Promotion ScalingInfo creation should not fail") + }) + } + Table::Store => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 6, 51, 201, 402, 501, 675, 750, 852, 951]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("Store ScalingInfo creation should not fail") + }) + } + Table::StoreReturns => { + // StoreReturns is generated as part of StoreSales (10% return rate) + // Its row count is derived from StoreSales, not independently scaled + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("StoreReturns ScalingInfo creation should not fail") + }) + } + Table::StoreSales => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 24, 240, 2400, 7200, 24000, 72000, 240000, 720000, 2400000, + ]; + ScalingInfo::new(4, ScalingModel::Linear, &row_counts, 0) + .expect("StoreSales ScalingInfo creation should not fail") + }) + } + Table::WebPage => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 30, 100, 1020, 1302, 1500, 1800, 2001, 2301, 2502]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("WebPage ScalingInfo creation should not fail") + }) + } + Table::WebReturns => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 60, 600, 6000, 18000, 60000, 180000, 600000, 1800000, 6000000, + ]; + ScalingInfo::new(3, ScalingModel::Linear, &row_counts, 0) + .expect("WebReturns ScalingInfo creation should not fail") + }) + } + Table::WebSales => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [ + 0, 60, 600, 6000, 18000, 60000, 180000, 600000, 1800000, 6000000, + ]; + ScalingInfo::new(3, ScalingModel::Linear, &row_counts, 0) + .expect("WebSales ScalingInfo creation should not fail") + }) + } + Table::WebSite => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 15, 21, 12, 21, 27, 33, 39, 42, 48]; + ScalingInfo::new(0, ScalingModel::Logarithmic, &row_counts, 0) + .expect("WebSite ScalingInfo creation should not fail") + }) + } + Table::DbgenVersion => { + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("DbgenVersion ScalingInfo creation should not fail") + }) + } + Table::Inventory => { + // Inventory row count = item_id_count × warehouse_count × weeks + // This is dynamically computed in the generator, these are placeholder values + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("Inventory ScalingInfo creation should not fail") + }) + } + Table::SStore => { + // Source table - no scaling info, use static 0 + static SCALING: OnceLock = OnceLock::new(); + SCALING.get_or_init(|| { + let row_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + ScalingInfo::new(0, ScalingModel::Static, &row_counts, 0) + .expect("SStore ScalingInfo creation should not fail") + }) + } + } + } + + /// Get regular column count for this table + pub fn get_column_count(&self) -> usize { + match self { + Table::CallCenter => CallCenterColumn::values().len(), + Table::CatalogPage => 9, // CatalogPageColumn has 9 columns + Table::CatalogReturns => 27, // CatalogReturnsColumn has 27 columns + Table::CatalogSales => 34, // CatalogSalesColumn has 34 columns + Table::Warehouse => 0, // TODO: Return WarehouseColumn::values().len() once WarehouseColumn is implemented + Table::ShipMode => 0, // TODO: Return ShipModeColumn::values().len() once ShipModeColumn is implemented + Table::Reason => 0, // TODO: Return ReasonColumn::values().len() once ReasonColumn is implemented + Table::IncomeBand => 0, // TODO: Return IncomeBandColumn::values().len() once IncomeBandColumn is implemented + Table::HouseholdDemographics => HouseholdDemographicsColumn::values().len(), + Table::CustomerDemographics => 0, // TODO: Return CustomerDemographicsColumn::values().len() once CustomerDemographicsColumn is implemented + Table::CustomerAddress => CustomerAddressColumn::values().len(), + Table::Customer => CustomerColumn::values().len(), + Table::DateDim => 0, // TODO: Return DateDimColumn::values().len() once DateDimColumn is implemented + Table::TimeDim => 0, // TODO: Return TimeDimColumn::values().len() once TimeDimColumn is implemented + Table::Item => 22, // ItemColumn has 22 columns (I_ITEM_SK to I_PRODUCT_NAME) + Table::Promotion => PromotionColumn::values().len(), + Table::Store => 29, // StoreColumn has 29 columns + Table::StoreReturns => 20, // StoreReturnsColumn has 20 columns + Table::StoreSales => 23, // StoreSalesColumn has 23 columns + Table::WebPage => 0, // TODO: Return WebPageColumn::values().len() once WebPageColumn is implemented + Table::WebReturns => 24, // WebReturnsColumn has 24 columns + Table::WebSales => 34, // WebSalesColumn has 34 columns + Table::WebSite => WebSiteColumn::values().len(), + Table::DbgenVersion => DbgenVersionColumn::values().len(), + Table::Inventory => InventoryColumn::values().len(), + Table::SStore => 0, // Source table + } + } + + /// Get generator column count for this table + pub fn get_generator_column_count(&self) -> usize { + match self { + Table::CallCenter => CallCenterGeneratorColumn::values().len(), + Table::CatalogPage => CatalogPageGeneratorColumn::all_variants().len(), + Table::CatalogReturns => CatalogReturnsGeneratorColumn::all_variants().len(), + Table::CatalogSales => CatalogSalesGeneratorColumn::all_variants().len(), + Table::Warehouse => WarehouseGeneratorColumn::values().len(), + Table::ShipMode => ShipModeGeneratorColumn::values().len(), + Table::Reason => ReasonGeneratorColumn::values().len(), + Table::IncomeBand => IncomeBandGeneratorColumn::values().len(), + Table::HouseholdDemographics => HouseholdDemographicsGeneratorColumn::values().len(), + Table::CustomerDemographics => CustomerDemographicsGeneratorColumn::values().len(), + Table::CustomerAddress => CustomerAddressGeneratorColumn::values().len(), + Table::Customer => CustomerGeneratorColumn::values().len(), + Table::DateDim => DateDimGeneratorColumn::values().len(), + Table::TimeDim => TimeDimGeneratorColumn::values().len(), + Table::Item => ItemGeneratorColumn::all_columns().len(), + Table::Promotion => PromotionGeneratorColumn::values().len(), + Table::Store => StoreGeneratorColumn::all_columns().len(), + Table::StoreReturns => StoreReturnsGeneratorColumn::all_variants().len(), + Table::StoreSales => StoreSalesGeneratorColumn::all_variants().len(), + Table::WebPage => WebPageGeneratorColumn::values().len(), + Table::WebReturns => WebReturnsGeneratorColumn::all_variants().len(), + Table::WebSales => WebSalesGeneratorColumn::all_variants().len(), + Table::WebSite => WebSiteGeneratorColumn::values().len(), + Table::DbgenVersion => DbgenVersionGeneratorColumn::values().len(), + Table::Inventory => InventoryGeneratorColumn::all_variants().len(), + Table::SStore => 0, // Source table + } + } + + /// Get a specific regular column by index + pub fn get_column_by_index(&self, index: usize) -> Option<&'static dyn Column> { + match self { + Table::CallCenter => { + let columns = CallCenterColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::CatalogPage => { + // TODO: Implement once CatalogPageColumn is created + None + } + Table::CatalogReturns => { + // TODO: Implement once CatalogReturnsColumn is created + None + } + Table::CatalogSales => { + // TODO: Implement once CatalogSalesColumn is created + None + } + Table::Warehouse => { + // TODO: Implement once WarehouseColumn is created + None + } + Table::ShipMode => { + // TODO: Implement once ShipModeColumn is created + None + } + Table::Reason => { + // TODO: Implement once ReasonColumn is created + None + } + Table::IncomeBand => { + // TODO: Implement once IncomeBandColumn is created + None + } + Table::HouseholdDemographics => { + let columns = HouseholdDemographicsColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::CustomerDemographics => { + // TODO: Implement once CustomerDemographicsColumn is created + None + } + Table::CustomerAddress => { + let columns = CustomerAddressColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::Customer => { + let columns = CustomerColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::DateDim => { + // TODO: Implement once DateDimColumn is created + None + } + Table::TimeDim => { + // TODO: Implement once TimeDimColumn is created + None + } + Table::Item => { + // TODO: Implement once ItemColumn is created + None + } + Table::Promotion => { + let columns = PromotionColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::Store => { + // TODO: Implement once StoreColumn is created + None + } + Table::StoreReturns => { + // TODO: Implement once StoreReturnsColumn is created + None + } + Table::StoreSales => { + // TODO: Implement once StoreSalesColumn is created + None + } + Table::WebPage => { + // TODO: Implement once WebPageColumn is created + None + } + Table::WebReturns => { + // TODO: Implement once WebReturnsColumn is created + None + } + Table::WebSales => { + // TODO: Implement once WebSalesColumn is created + None + } + Table::WebSite => { + let columns = WebSiteColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::DbgenVersion => { + let columns = DbgenVersionColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::Inventory => { + let columns = InventoryColumn::values(); + columns.get(index).map(|col| col as &dyn Column) + } + Table::SStore => None, // Source table + } + } + + /// Get a specific generator column by index + pub fn get_generator_column_by_index( + &self, + index: usize, + ) -> Option<&'static dyn GeneratorColumn> { + match self { + Table::CallCenter => { + let columns = CallCenterGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::CatalogPage => { + let columns = CatalogPageGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::CatalogReturns => { + let columns = CatalogReturnsGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::CatalogSales => { + let columns = CatalogSalesGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Warehouse => { + let columns = WarehouseGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::ShipMode => { + let columns = ShipModeGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Reason => { + let columns = ReasonGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::IncomeBand => { + let columns = IncomeBandGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::HouseholdDemographics => { + let columns = HouseholdDemographicsGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::CustomerDemographics => { + let columns = CustomerDemographicsGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::CustomerAddress => { + let columns = CustomerAddressGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Customer => { + let columns = CustomerGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::DateDim => { + let columns = DateDimGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::TimeDim => { + let columns = TimeDimGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Item => { + let columns = ItemGeneratorColumn::all_columns(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Promotion => { + let columns = PromotionGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Store => { + let columns = StoreGeneratorColumn::all_columns(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::StoreReturns => { + let columns = StoreReturnsGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::StoreSales => { + let columns = StoreSalesGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::WebPage => { + static COLUMNS: OnceLock> = OnceLock::new(); + let columns = COLUMNS.get_or_init(|| WebPageGeneratorColumn::values().to_vec()); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::WebReturns => { + let columns = WebReturnsGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::WebSales => { + let columns = WebSalesGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::WebSite => { + static COLUMNS: OnceLock> = OnceLock::new(); + let columns = COLUMNS.get_or_init(|| WebSiteGeneratorColumn::values().to_vec()); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::DbgenVersion => { + let columns = DbgenVersionGeneratorColumn::values(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::Inventory => { + let columns = InventoryGeneratorColumn::all_variants(); + columns.get(index).map(|col| col as &dyn GeneratorColumn) + } + Table::SStore => None, // Source table + } + } + + /// Get a specific column by name (case-insensitive) + pub fn get_column(&self, column_name: &str) -> Result<&'static dyn Column> { + let column_name_lower = column_name.to_lowercase(); + let column_count = self.get_column_count(); + + let mut found_column = None; + for i in 0..column_count { + if let Some(col) = self.get_column_by_index(i) { + if col.get_name().to_lowercase() == column_name_lower { + if found_column.is_some() { + return Err(crate::TpcdsError::new(&format!( + "Multiple columns found matching '{}' in table '{}'", + column_name, + self.get_name() + ))); + } + found_column = Some(col); + } + } + } + + found_column.ok_or_else(|| { + crate::TpcdsError::new(&format!( + "Column '{}' not found in table '{}'", + column_name, + self.get_name() + )) + }) + } + + /// Check if this table keeps history + pub fn keeps_history(&self) -> bool { + self.get_table_flags().keeps_history() + } + + /// Check if this is a small table + pub fn is_small(&self) -> bool { + self.get_table_flags().is_small() + } + + /// Check if this table is date-based + pub fn is_date_based(&self) -> bool { + self.get_table_flags().is_date_based() + } + + /// Get all base tables (non-source tables) + pub fn get_base_tables() -> Vec
{ + vec![ + Table::CallCenter, + Table::Warehouse, + Table::ShipMode, + Table::Reason, + Table::IncomeBand, + Table::HouseholdDemographics, + Table::CustomerDemographics, + Table::CustomerAddress, + Table::Customer, + Table::DateDim, + Table::TimeDim, + Table::Item, + Table::Promotion, + Table::Store, + Table::WebPage, + Table::WebSite, + ] // TODO: Add other tables as implemented + } + + /// Get a table by name (case-insensitive) + pub fn get_table(table_name: &str) -> Result
{ + let table_name_lower = table_name.to_lowercase(); + let base_tables = Self::get_base_tables(); + + let matches: Vec<_> = base_tables + .iter() + .filter(|table| table.get_name() == table_name_lower) + .collect(); + + if matches.len() == 1 { + Ok(*matches[0]) + } else if matches.is_empty() { + Err(crate::TpcdsError::new(&format!( + "Table '{}' not found", + table_name + ))) + } else { + Err(crate::TpcdsError::new(&format!( + "Multiple tables found matching '{}'", + table_name + ))) + } + } +} + +impl std::fmt::Display for Table { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.get_name()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::column::ColumnTypeBase; + + #[test] + fn test_table_name() { + assert_eq!(Table::CallCenter.get_name(), "call_center"); + assert_eq!(format!("{}", Table::CallCenter), "call_center"); + } + + #[test] + fn test_table_flags() { + let flags = Table::CallCenter.get_table_flags(); + assert!(flags.keeps_history()); + assert!(flags.is_small()); + assert!(!flags.is_date_based()); + } + + #[test] + fn test_table_metadata() { + assert_eq!(Table::CallCenter.get_null_basis_points(), 100); + assert_eq!(Table::CallCenter.get_not_null_bit_map(), 0xB); + } + + #[test] + fn test_scaling_info() { + let scaling = Table::CallCenter.get_scaling_info(); + assert_eq!(scaling.get_multiplier(), 0); + assert_eq!(scaling.get_scaling_model(), ScalingModel::Logarithmic); + + // Test specific scale values from Java + assert_eq!(scaling.get_row_count_for_scale(1.0).unwrap(), 3); + assert_eq!(scaling.get_row_count_for_scale(100000.0).unwrap(), 30); + } + + #[test] + fn test_get_columns() { + let table = Table::CallCenter; + assert_eq!(table.get_column_count(), 31); + + // Test first column + let first_col = table.get_column_by_index(0).unwrap(); + assert_eq!(first_col.get_name(), "cc_call_center_sk"); + assert_eq!(first_col.get_position(), 0); + + // Convert column table to our table type for comparison + let column_table: Table = first_col.get_table().into(); + assert_eq!(column_table, Table::CallCenter); + } + + #[test] + fn test_get_generator_columns() { + let table = Table::CallCenter; + assert_eq!(table.get_generator_column_count(), 34); + + // Test first generator column + let first_gen_col = table.get_generator_column_by_index(0).unwrap(); + assert_eq!(first_gen_col.get_global_column_number(), 1); + + // Convert generator column table to our table type for comparison + let gen_column_table: Table = first_gen_col.get_table().into(); + assert_eq!(gen_column_table, Table::CallCenter); + } + + #[test] + fn test_get_column_by_name() { + let table = Table::CallCenter; + + // Test exact match + let column = table.get_column("cc_call_center_sk").unwrap(); + assert_eq!(column.get_name(), "cc_call_center_sk"); + + // Test case insensitive + let column = table.get_column("CC_CALL_CENTER_SK").unwrap(); + assert_eq!(column.get_name(), "cc_call_center_sk"); + + // Test not found + assert!(table.get_column("nonexistent_column").is_err()); + } + + #[test] + fn test_table_flags_methods() { + let table = Table::CallCenter; + assert!(table.keeps_history()); + assert!(table.is_small()); + assert!(!table.is_date_based()); + } + + #[test] + fn test_get_table_by_name() { + // Test exact match + let table = Table::get_table("call_center").unwrap(); + assert_eq!(table, Table::CallCenter); + + // Test case insensitive + let table = Table::get_table("CALL_CENTER").unwrap(); + assert_eq!(table, Table::CallCenter); + + // Test not found + assert!(Table::get_table("nonexistent_table").is_err()); + } + + #[test] + fn test_table_conversions() { + let table = Table::CallCenter; + let column_table: crate::column::Table = table.into(); + let back_to_table: Table = column_table.into(); + assert_eq!(table, back_to_table); + } + + #[test] + fn test_column_types_integration() { + let table = Table::CallCenter; + + // Test some specific column types by finding them by name + let sk_column = table.get_column("cc_call_center_sk").unwrap(); + assert_eq!(sk_column.get_type().get_base(), ColumnTypeBase::Identifier); + + let name_column = table.get_column("cc_name").unwrap(); + assert_eq!(name_column.get_type().get_base(), ColumnTypeBase::Varchar); + assert_eq!(name_column.get_type().get_precision(), Some(50)); + + let date_column = table.get_column("cc_rec_start_date").unwrap(); + assert_eq!(date_column.get_type().get_base(), ColumnTypeBase::Date); + } + + #[test] + fn test_generator_vs_regular_column_count() { + let table = Table::CallCenter; + + assert_eq!(table.get_column_count(), 31); // User-visible columns + assert_eq!(table.get_generator_column_count(), 34); // Generator columns (includes address, scd, nulls) + } + + #[test] + fn test_singleton_behavior() { + // Test that repeated calls return the same references + let flags1 = Table::CallCenter.get_table_flags(); + let flags2 = Table::CallCenter.get_table_flags(); + assert!(std::ptr::eq(flags1, flags2)); + + let scaling1 = Table::CallCenter.get_scaling_info(); + let scaling2 = Table::CallCenter.get_scaling_info(); + assert!(std::ptr::eq(scaling1, scaling2)); + } +} diff --git a/tpcdsgen/src/table_flags.rs b/tpcdsgen/src/table_flags.rs new file mode 100644 index 00000000..9f6b36d9 --- /dev/null +++ b/tpcdsgen/src/table_flags.rs @@ -0,0 +1,181 @@ +/// Table flags indicating special properties (TableFlags) +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct TableFlags { + /// FL_TYPE_2 in the C code. This dimension keeps history -- rowcount shows unique entities (not including revisions). + keeps_history: bool, + /// This table has low rowcount; used by Address.java + is_small: bool, + /// This table is date-based for generation + is_date_based: bool, +} + +impl TableFlags { + /// Create new TableFlags with specified flags (const-compatible) + pub const fn new(keeps_history: bool, is_small: bool, is_date_based: bool) -> Self { + Self { + keeps_history, + is_small, + is_date_based, + } + } + + /// Check if this table keeps history + pub fn keeps_history(&self) -> bool { + self.keeps_history + } + + /// Check if this is a small table + pub fn is_small(&self) -> bool { + self.is_small + } + + /// Check if this table is date-based + pub fn is_date_based(&self) -> bool { + self.is_date_based + } +} + +/// Builder for TableFlags (TableFlagsBuilder) +#[derive(Debug, Default)] +pub struct TableFlagsBuilder { + keeps_history: bool, + is_small: bool, + is_date_based: bool, +} + +impl TableFlagsBuilder { + /// Create a new builder + pub fn new() -> Self { + Self::default() + } + + /// Set the keeps_history flag + pub fn set_keeps_history(mut self) -> Self { + self.keeps_history = true; + self + } + + /// Set the is_small flag + pub fn set_is_small(mut self) -> Self { + self.is_small = true; + self + } + + /// Set the is_date_based flag + pub fn set_is_date_based(mut self) -> Self { + self.is_date_based = true; + self + } + + /// Build the final TableFlags + pub fn build(self) -> TableFlags { + TableFlags::new(self.keeps_history, self.is_small, self.is_date_based) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_table_flags_creation() { + let flags = TableFlags::new(true, false, true); + assert!(flags.keeps_history()); + assert!(!flags.is_small()); + assert!(flags.is_date_based()); + } + + #[test] + fn test_table_flags_default() { + let flags = TableFlags::default(); + assert!(!flags.keeps_history()); + assert!(!flags.is_small()); + assert!(!flags.is_date_based()); + } + + #[test] + fn test_table_flags_builder_empty() { + let flags = TableFlagsBuilder::new().build(); + assert!(!flags.keeps_history()); + assert!(!flags.is_small()); + assert!(!flags.is_date_based()); + } + + #[test] + fn test_table_flags_builder_all_flags() { + let flags = TableFlagsBuilder::new() + .set_keeps_history() + .set_is_small() + .set_is_date_based() + .build(); + + assert!(flags.keeps_history()); + assert!(flags.is_small()); + assert!(flags.is_date_based()); + } + + #[test] + fn test_table_flags_builder_partial() { + let flags = TableFlagsBuilder::new() + .set_keeps_history() + .set_is_date_based() + .build(); + + assert!(flags.keeps_history()); + assert!(!flags.is_small()); + assert!(flags.is_date_based()); + } + + #[test] + fn test_table_flags_builder_chaining() { + // Test that builder methods can be chained in any order + let flags1 = TableFlagsBuilder::new() + .set_is_small() + .set_keeps_history() + .build(); + + let flags2 = TableFlagsBuilder::new() + .set_keeps_history() + .set_is_small() + .build(); + + assert_eq!(flags1, flags2); + assert!(flags1.keeps_history()); + assert!(flags1.is_small()); + assert!(!flags1.is_date_based()); + } + + #[test] + fn test_table_flags_equality() { + let flags1 = TableFlags::new(true, false, true); + let flags2 = TableFlags::new(true, false, true); + let flags3 = TableFlags::new(false, false, true); + + assert_eq!(flags1, flags2); + assert_ne!(flags1, flags3); + } + + #[test] + fn test_table_flags_clone() { + let flags1 = TableFlags::new(true, true, false); + let flags2 = flags1.clone(); + + assert_eq!(flags1, flags2); + assert!(flags2.keeps_history()); + assert!(flags2.is_small()); + assert!(!flags2.is_date_based()); + } + + #[test] + fn test_java_style_usage() { + // Test usage pattern matching Java: new TableFlagsBuilder().setKeepsHistory().setIsSmall().build() + let flags = TableFlagsBuilder::new() + .set_keeps_history() + .set_is_small() + .build(); + + assert!(flags.keeps_history()); + assert!(flags.is_small()); + assert!(!flags.is_date_based()); + } +} diff --git a/tpcdsgen/src/types/address.rs b/tpcdsgen/src/types/address.rs new file mode 100644 index 00000000..a1fdcbe9 --- /dev/null +++ b/tpcdsgen/src/types/address.rs @@ -0,0 +1,404 @@ +use crate::{check_argument, error::Result, TpcdsError}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Address { + suite_number: String, + street_number: i32, + street_name1: String, + street_name2: String, + street_type: String, + city: String, + county: Option, + state: String, + country: String, + zip: i32, + gmt_offset: i32, +} + +#[allow(clippy::too_many_arguments)] +impl Address { + pub fn new( + suite_number: String, + street_number: i32, + street_name1: String, + street_name2: String, + street_type: String, + city: String, + county: Option, + state: String, + country: String, + zip: i32, + gmt_offset: i32, + ) -> Result { + check_argument!( + (1..=1000).contains(&street_number), + "streetNumber is not between 1 and 1000" + ); + check_argument!((0..=99999).contains(&zip), "zip is not between 0 and 99999"); + + Ok(Address { + suite_number, + street_number, + street_name1, + street_name2, + street_type, + city, + county, + state, + country, + zip, + gmt_offset, + }) + } + + pub fn get_street_number(&self) -> i32 { + self.street_number + } + + pub fn get_street_name(&self) -> String { + format!("{} {}", self.street_name1, self.street_name2) + } + + pub fn get_suite_number(&self) -> &str { + &self.suite_number + } + + pub fn get_street_type(&self) -> &str { + &self.street_type + } + + pub fn get_city(&self) -> &str { + &self.city + } + + pub fn get_county(&self) -> Option<&str> { + self.county.as_deref() + } + + pub fn get_state(&self) -> &str { + &self.state + } + + pub fn get_zip(&self) -> i32 { + self.zip + } + + pub fn get_country(&self) -> &str { + &self.country + } + + pub fn get_gmt_offset(&self) -> i32 { + self.gmt_offset + } + + pub fn get_street_name1(&self) -> &str { + &self.street_name1 + } + + pub fn get_street_name2(&self) -> &str { + &self.street_name2 + } + + /// Create a new AddressBuilder + pub fn builder() -> AddressBuilder { + AddressBuilder::new() + } + + // Static method to compute city hash (implementation exactly) + pub fn compute_city_hash(name: &str) -> i32 { + let mut hash_value = 0i32; + let mut result = 0i32; + + for ch in name.chars() { + hash_value = hash_value.wrapping_mul(26); + hash_value = hash_value.wrapping_add((ch as i32) - ('A' as i32)); + if hash_value > 1000000 { + hash_value %= 10000; + result = result.wrapping_add(hash_value); + hash_value = 0; + } + } + + hash_value %= 1000; + result = result.wrapping_add(hash_value); + result % 10000 // looking for a 4 digit result + } + + // Static method to create address for a specific table column (implementation exactly) + pub fn make_address_for_column( + table: crate::table::Table, + stream: &mut dyn crate::random::stream::RandomNumberStream, + scaling: &crate::config::Scaling, + ) -> Result { + use crate::distribution::{ + get_city_at_index, pick_random_city, pick_random_street_name, pick_random_street_type, + CitiesWeights, FipsCountyDistribution, FipsWeights, StreetNamesWeights, + }; + use crate::pseudo_table_scaling_infos::PseudoTableScalingInfos; + use crate::random::RandomValueGenerator; + + let street_number = RandomValueGenerator::generate_uniform_random_int(1, 1000, stream); + let street_name1 = pick_random_street_name(StreetNamesWeights::Default, stream) + .unwrap_or("Main") + .to_string(); + let street_name2 = pick_random_street_name(StreetNamesWeights::HalfEmpty, stream) + .unwrap_or("") + .to_string(); + let street_type = pick_random_street_type(stream) + .unwrap_or("Street") + .to_string(); + + let random_int = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + let suite_number = if random_int % 2 == 1 { + // if i is odd, suiteNumber is a number + format!("Suite {}", (random_int / 2) * 10) + } else { + // if i is even, suiteNumber is a letter + format!( + "Suite {}", + ((random_int / 2) % 25 + ('A' as i32)) as u8 as char + ) + }; + + let config_table = match table { + crate::table::Table::CallCenter => crate::config::table::Table::CallCenter, + crate::table::Table::WebSite => crate::config::table::Table::WebSite, + crate::table::Table::Warehouse => crate::config::table::Table::Warehouse, + crate::table::Table::CustomerAddress => crate::config::table::Table::CustomerAddress, + crate::table::Table::Store => crate::config::table::Table::Store, + _ => panic!( + "Table {:?} not yet supported in Address::make_address_for_column", + table + ), + }; + let row_count = scaling.get_row_count(config_table) as i32; + let city = if table.is_small() { + let max_cities = + PseudoTableScalingInfos::get_active_cities_row_count_for_scale(scaling.get_scale()) + as i32; + let random_int = RandomValueGenerator::generate_uniform_random_int( + 0, + if max_cities > row_count { + row_count - 1 + } else { + max_cities - 1 + }, + stream, + ); + get_city_at_index(random_int as usize) + .unwrap_or("Midway") + .to_string() + } else { + pick_random_city(CitiesWeights::UnifiedStepFunction, stream) + .unwrap_or("Midway") + .to_string() + }; + + // county is picked from a distribution, based on population and keys the rest + let region_number = if table.is_small() { + let max_counties = PseudoTableScalingInfos::get_active_counties_row_count_for_scale( + scaling.get_scale(), + ) as i32; + RandomValueGenerator::generate_uniform_random_int( + 0, + if max_counties > row_count { + row_count - 1 + } else { + max_counties - 1 + }, + stream, + ) as usize + } else { + FipsCountyDistribution::pick_random_index(FipsWeights::Uniform, stream).unwrap_or(0) + }; + + let county = FipsCountyDistribution::get_county_at_index(region_number) + .unwrap_or("Williamson County"); + // let county = if table.is_small() { + // FipsCountyDistribution::get_county_at_index(region_number) + // .unwrap_or("Williamson County") + // } else { + // // stubbed. + // FipsCountyDistribution::get_county_at_index(region_number) + // .unwrap_or("Williamson County") + // }; + + // match state with the selected region/county + let state = + FipsCountyDistribution::get_state_abbreviation_at_index(region_number).unwrap_or("TN"); + + // match the zip prefix with the selected region/county + let mut zip = Self::compute_city_hash(&city); + + // 00000 - 00600 are unused. Avoid them + let zip_prefix = + FipsCountyDistribution::get_zip_prefix_at_index(region_number).unwrap_or(0); + if zip_prefix == 0 && zip < 9400 { + zip += 600; + } + zip += zip_prefix * 10000; + + let gmt_offset = + FipsCountyDistribution::get_gmt_offset_at_index(region_number).unwrap_or(-5); + let country = "United States"; + + Address::new( + suite_number, + street_number, + street_name1, + street_name2, + street_type, + city, + Some(county.to_string()), + state.to_string(), + country.to_string(), + zip, + gmt_offset, + ) + } +} + +/// Builder for Address +#[derive(Debug, Default)] +pub struct AddressBuilder { + suite_number: Option, + street_number: Option, + street_name1: Option, + street_name2: Option, + street_type: Option, + city: Option, + county: Option, + state: Option, + country: Option, + zip: Option, + gmt_offset: Option, +} + +impl AddressBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn suite_number(mut self, value: String) -> Self { + self.suite_number = Some(value); + self + } + + pub fn street_number(mut self, value: i32) -> Self { + self.street_number = Some(value); + self + } + + pub fn street_name(mut self, value: String) -> Self { + self.street_name1 = Some(value); + self + } + + pub fn street_name1(mut self, value: String) -> Self { + self.street_name1 = Some(value); + self + } + + pub fn street_name2(mut self, value: String) -> Self { + self.street_name2 = Some(value); + self + } + + pub fn street_type(mut self, value: String) -> Self { + self.street_type = Some(value); + self + } + + pub fn city(mut self, value: String) -> Self { + self.city = Some(value); + self + } + + pub fn county(mut self, value: String) -> Self { + self.county = Some(value); + self + } + + pub fn state(mut self, value: String) -> Self { + self.state = Some(value); + self + } + + pub fn country(mut self, value: String) -> Self { + self.country = Some(value); + self + } + + pub fn zip(mut self, value: i32) -> Self { + self.zip = Some(value); + self + } + + pub fn gmt_offset(mut self, value: i32) -> Self { + self.gmt_offset = Some(value); + self + } + + pub fn build(self) -> Address { + Address { + suite_number: self.suite_number.unwrap_or_default(), + street_number: self.street_number.unwrap_or(1), + street_name1: self.street_name1.unwrap_or_default(), + street_name2: self.street_name2.unwrap_or_default(), + street_type: self.street_type.unwrap_or_default(), + city: self.city.unwrap_or_default(), + county: self.county, + state: self.state.unwrap_or_default(), + country: self.country.unwrap_or_default(), + zip: self.zip.unwrap_or(0), + gmt_offset: self.gmt_offset.unwrap_or(0), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_address_creation() { + let address = Address::new( + "Suite 100".to_string(), + 123, + "Main".to_string(), + "Street".to_string(), + "St".to_string(), + "Anytown".to_string(), + Some("AnyCounty".to_string()), + "CA".to_string(), + "United States".to_string(), + 12345, + -8, + ) + .unwrap(); + + assert_eq!(address.get_street_number(), 123); + assert_eq!(address.get_street_name(), "Main Street"); + assert_eq!(address.get_city(), "Anytown"); + assert_eq!(address.get_zip(), 12345); + } + + #[test] + fn test_city_hash() { + let hash = Address::compute_city_hash("TESTCITY"); + assert!(hash >= 0 && hash < 10000); + } + + #[test] + fn test_address_builder() { + let address = Address::builder() + .street_number(456) + .city("TestCity".to_string()) + .zip(54321) + .build(); + + assert_eq!(address.get_street_number(), 456); + assert_eq!(address.get_city(), "TestCity"); + assert_eq!(address.get_zip(), 54321); + } +} diff --git a/tpcdsgen/src/types/date.rs b/tpcdsgen/src/types/date.rs new file mode 100644 index 00000000..2db21ac1 --- /dev/null +++ b/tpcdsgen/src/types/date.rs @@ -0,0 +1,372 @@ +use crate::{check_argument, check_state, error::Result, TpcdsError}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Date { + year: i32, + month: i32, + day: i32, +} + +impl Date { + // Constants matching Java implementation + pub const JULIAN_DATA_START_DATE: i64 = 2450815; // toJulianDays(Date::new(1998, 1, 1)) + pub const JULIAN_DATA_END_DATE: i64 = 2453005; // toJulianDays(Date::new(2003, 12, 31)) + pub const TODAYS_DATE: Date = Date { + year: 2003, + month: 1, + day: 8, + }; + pub const JULIAN_TODAYS_DATE: i32 = 2452648; // toJulianDays(TODAYS_DATE) = 2003-01-08 + pub const CURRENT_QUARTER: i32 = 1; + pub const CURRENT_WEEK: i32 = 2; + + pub const DATE_MAXIMUM: Date = Date { + year: 2002, + month: 12, + day: 31, + }; + pub const DATE_MINIMUM: Date = Date { + year: 1998, + month: 1, + day: 1, + }; + pub const JULIAN_DATE_MAXIMUM: i32 = 2452640; // toJulianDays(DATE_MAXIMUM) + pub const JULIAN_DATE_MINIMUM: i32 = 2450815; // toJulianDays(DATE_MINIMUM) + + pub const WEEKDAY_NAMES: [&'static str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ]; + + // Month day cumulative arrays (0-indexed for convenience, but month 0 is unused) + const MONTH_DAYS: [i32; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + const MONTH_DAYS_LEAP_YEAR: [i32; 13] = + [0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]; + + // Const version for compile-time date creation (no validation) + pub const fn new(year: i32, month: i32, day: i32) -> Self { + Date { year, month, day } + } + + // Safe version with validation + pub fn new_validated(year: i32, month: i32, day: i32) -> Result { + check_argument!(year > 0, "Year must be a positive value"); + check_argument!( + month > 0 && month <= 12, + "Month must be a number between 1 and 12 (inclusive)" + ); + check_argument!(day > 0 && day <= Self::get_days_in_month(month, year)?, + "Day must be a positive value and cannot exceed the maximum number of days in the month"); + + Ok(Date { year, month, day }) + } + + // Algorithm: Fleigel and Van Flandern (CACM, vol 11, #10, Oct. 1968, p. 657) + pub fn from_julian_days(julian_days: i32) -> Self { + let l = julian_days + 68569; + let n = (4 * l) / 146097; + let l = l - (146097 * n + 3) / 4; + let i = (4000 * (l + 1)) / 1461001; + let l = l - (1461 * i) / 4 + 31; + let j = (80 * l) / 2447; + + let day = l - (2447 * j) / 80; + let l = j / 11; + let month = j + 2 - 12 * l; + let year = 100 * (n - 49) + i + l; + + Self::new(year, month, day) + } + + // http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + pub fn to_julian_days(&self) -> i32 { + let mut month = self.month; + let mut year = self.year; + + // Start years in March so you don't have to account for February. + if month <= 2 { + month += 12; + year -= 1; + } + + let days_bce_in_julian_epoch = 1721118; // Days Before the Common Era (before year 1) in the Julian Epoch + + // The month calculation looks convoluted, but can be thought of as follows: + // There are a little over 30.6 (153/5) days in a month (excluding February) + // Subtract 3 months because we start from the third month and don't include the current month + // (153/5 * 3 = 459/5) + // adding another 2/5 gets you 31 days at the right times. + self.day + + (153 * month - 457) / 5 + + 365 * year + year / 4 - year / 100 + year / 400 + // 365 days in a year + leap years + days_bce_in_julian_epoch + 1 + } + + // This is NOT a correct computation of leap years. + // There is a bug in the C code that doesn't handle century years correctly. + pub fn is_leap_year(year: i32) -> bool { + year % 4 == 0 + } + + pub fn get_days_in_year(year: i32) -> i32 { + if Self::is_leap_year(year) { + 366 + } else { + 365 + } + } + + pub fn get_day(&self) -> i32 { + self.day + } + + pub fn get_year(&self) -> i32 { + self.year + } + + pub fn get_month(&self) -> i32 { + self.month + } + + fn get_days_in_month(month: i32, year: i32) -> Result { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => Ok(31), + 4 | 6 | 9 | 11 => Ok(30), + 2 => { + if Self::is_leap_year(year) { + Ok(29) + } else { + Ok(28) + } + } + _ => { + check_state!(false, &format!("Invalid value for month {}", month)); + unreachable!() + } + } + } + + // The ordinal reference into the calendar distribution for a given date + pub fn get_day_index(&self) -> i32 { + Self::get_days_through_first_of_month(self) + self.get_day() + } + + fn get_days_through_first_of_month(date: &Date) -> i32 { + if Self::is_leap_year(date.get_year()) { + Self::MONTH_DAYS_LEAP_YEAR[date.get_month() as usize] + } else { + Self::MONTH_DAYS[date.get_month() as usize] + } + } + + pub fn compute_first_date_of_month(&self) -> Result { + Ok(Self::new(self.year, self.month, 1)) + } + + pub fn compute_last_date_of_month(&self) -> Result { + // Copies a bug in the C code that adds all the days in the year + // through the first of month instead of just the number of days in the month + let julian_days = + self.to_julian_days() - self.day + Self::get_days_through_first_of_month(self); + Ok(Self::from_julian_days(julian_days)) + } + + pub fn compute_same_day_last_year(&self) -> Result { + let mut day = self.day; + if Self::is_leap_year(self.year) && self.month == 2 && self.day == 29 { + day = 28; + } + Ok(Self::new(self.year - 1, self.month, day)) + } + + pub fn compute_same_day_last_quarter(&self) -> Result { + let quarter = (self.month - 1) / 3; // zero-indexed quarter number + let julian_start_of_quarter = Self::new(self.year, quarter * 3 + 1, 1).to_julian_days(); + let julian_date = self.to_julian_days(); + let distance_from_start = julian_date - julian_start_of_quarter; + + let last_quarter = if quarter > 0 { quarter - 1 } else { 3 }; + let last_quarter_year = if quarter > 0 { + self.year + } else { + self.year - 1 + }; + let julian_start_of_previous_quarter = + Self::new(last_quarter_year, last_quarter * 3 + 1, 1).to_julian_days(); + + Ok(Self::from_julian_days( + julian_start_of_previous_quarter + distance_from_start, + )) + } + + // Uses the doomsday algorithm to calculate the day of the week. + // The doomsday algorithm is based on the knowledge that our calendar + // repeats itself every 400 years. Additionally, there are certain easy + // to remember dates in the year that always fall on the same day as each + // other. The day of the week on which these dates fall is referred to as doomsday. + // https://en.wikipedia.org/wiki/Doomsday_rule + pub fn compute_day_of_week(&self) -> i32 { + // doomsdays for the first year of each century in a 400 year cycle + let century_anchors = [3, 2, 0, 5]; + + // Dates in each month that are known to fall on the same day of the week as each other. + // The zero at index zero is just a place holder because months are 1-indexed. + // Other values of zero refer to the last day of the previous month. + let mut known = [0, 3, 0, 0, 4, 9, 6, 11, 8, 5, 10, 7, 12]; + + let year = self.get_year(); + if Self::is_leap_year(self.get_year()) { + // adjust the known dates for January and February + known[1] = 4; + known[2] = 1; + } + + // calculate the doomsday for the century + let mut century_index = year / 100; + century_index -= 15; // the year 1500 would be at index zero + century_index %= 4; // which century are we in in the 400 year cycle + let century_anchor = century_anchors[century_index as usize]; + + // and then calculate the doomsday for the year + let year_of_century = year % 100; + let q = year_of_century / 12; + let r = year_of_century % 12; + let s = r / 4; + let mut doomsday = century_anchor + q + r + s; + doomsday %= 7; + + // finally, calculate the day of week for our date + let mut result = self.get_day(); + result -= known[self.get_month() as usize]; + while result < 0 { + result += 7; + } + while result > 6 { + result -= 7; + } + + result += doomsday; + result % 7 + } + + // Pre-computed constants for the static methods + pub fn julian_data_start_date() -> i64 { + Self::JULIAN_DATA_START_DATE + } + + pub fn julian_data_end_date() -> i64 { + Self::JULIAN_DATA_END_DATE + } + + pub fn todays_date() -> Date { + Self::TODAYS_DATE + } + + pub fn julian_todays_date() -> i32 { + Self::JULIAN_TODAYS_DATE + } + + pub fn date_maximum() -> Date { + Self::DATE_MAXIMUM + } + + pub fn date_minimum() -> Date { + Self::DATE_MINIMUM + } + + pub fn julian_date_maximum() -> i32 { + Self::JULIAN_DATE_MAXIMUM + } + + pub fn julian_date_minimum() -> i32 { + Self::JULIAN_DATE_MINIMUM + } + + // Helper function to convert Julian date to formatted string + pub fn julian_to_date_string(julian_days: i64) -> String { + let date = Self::from_julian_days(julian_days as i32); + date.to_string() + } + + // Convenience methods for cleaner API + pub fn year(&self) -> i32 { + self.year + } + + pub fn month(&self) -> i32 { + self.month + } + + pub fn day(&self) -> i32 { + self.day + } + + pub fn day_of_week(&self) -> i32 { + self.compute_day_of_week() + } + + pub fn day_of_year(&self) -> i32 { + self.get_day_index() + } + + pub fn last_day_of_month(&self) -> Date { + // Using unwrap is safe here because we're constructing from valid dates + self.compute_last_date_of_month().unwrap() + } + + pub fn same_day_last_year(&self) -> Date { + self.compute_same_day_last_year().unwrap() + } + + pub fn same_day_last_quarter(&self) -> Date { + self.compute_same_day_last_quarter().unwrap() + } +} + +impl std::fmt::Display for Date { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_date_creation() { + let date = Date::new(2003, 1, 8); + assert_eq!(date.get_year(), 2003); + assert_eq!(date.get_month(), 1); + assert_eq!(date.get_day(), 8); + } + + #[test] + fn test_julian_conversion() { + let date = Date::new(1998, 1, 1); + let julian = date.to_julian_days(); + assert_eq!(julian as i64, Date::JULIAN_DATA_START_DATE); + + let back = Date::from_julian_days(julian); + assert_eq!(back, date); + } + + #[test] + fn test_leap_year_bug() { + // Test the intentional bug - 1900 should not be a leap year but this function says it is + assert!(Date::is_leap_year(1900)); // Bug: should be false + assert!(Date::is_leap_year(2000)); // Correct: should be true + assert!(!Date::is_leap_year(2001)); // Correct: should be false + } + + #[test] + fn test_display() { + let date = Date::new(2003, 1, 8); + assert_eq!(format!("{}", date), "2003-01-08"); + } +} diff --git a/tpcdsgen/src/types/decimal.rs b/tpcdsgen/src/types/decimal.rs new file mode 100644 index 00000000..1b24747a --- /dev/null +++ b/tpcdsgen/src/types/decimal.rs @@ -0,0 +1,219 @@ +use crate::{check_argument, error::Result, TpcdsError}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Decimal { + // XXX: Definitions of precision and scale are reversed. This was done to + // make it easier to follow the C code, which reverses the definitions. Here, + // precision means the number of decimal places and scale means the total number + // of digits. We leave out the scale field because it's never used, and the C implementation + // was buggy. + precision: i32, + number: i64, +} + +impl Decimal { + pub const ZERO: Decimal = Decimal { + number: 0, + precision: 2, + }; + pub const ONE_HALF: Decimal = Decimal { + number: 50, + precision: 2, + }; + pub const NINE_PERCENT: Decimal = Decimal { + number: 9, + precision: 2, + }; + pub const ONE_HUNDRED: Decimal = Decimal { + number: 10000, + precision: 2, + }; + pub const ONE: Decimal = Decimal { + number: 100, + precision: 2, + }; + + pub fn new(number: i64, precision: i32) -> Result { + check_argument!( + precision >= 0, + "precision must be greater than or equal to zero" + ); + Ok(Decimal { precision, number }) + } + + pub fn parse_decimal(decimal_string: &str) -> Result { + let number: i64; + let precision: i32; + + if let Some(decimal_point_index) = decimal_string.find('.') { + let fractional = &decimal_string[decimal_point_index + 1..]; + precision = fractional.len() as i32; + let integer_part = &decimal_string[..decimal_point_index]; + let combined = format!("{}{}", integer_part, fractional); + number = combined + .parse::() + .map_err(|_| crate::TpcdsError::new("Failed to parse decimal string"))?; + } else { + number = decimal_string + .parse::() + .map_err(|_| crate::TpcdsError::new("Failed to parse decimal string"))?; + precision = 0; + } + + Self::new(number, precision) + } + + pub fn add2(decimal1: Decimal, decimal2: Decimal) -> Decimal { + let precision = if decimal1.precision > decimal2.precision { + decimal1.precision + } else { + decimal2.precision + }; + // This is not mathematically correct when the precisions aren't the same, but it's what the C code does + let number = decimal1.number + decimal2.number; + Decimal { number, precision } + } + + pub fn subtract(decimal1: Decimal, decimal2: Decimal) -> Decimal { + let precision = if decimal1.precision > decimal2.precision { + decimal1.precision + } else { + decimal2.precision + }; + // again following C code + let number = decimal1.number - decimal2.number; + Decimal { number, precision } + } + + pub fn multiply(decimal1: Decimal, decimal2: Decimal) -> Decimal { + let precision = if decimal1.precision > decimal2.precision { + decimal1.precision + } else { + decimal2.precision + }; + let mut number = decimal1.number * decimal2.number; + for _i in (precision + 1)..=(decimal1.precision + decimal2.precision) { + number /= 10; // Always round down, I guess + } + Decimal { number, precision } + } + + pub fn divide(decimal1: Decimal, decimal2: Decimal) -> Decimal { + let mut f1 = decimal1.number as f32; + let precision = if decimal1.precision > decimal2.precision { + decimal1.precision + } else { + decimal2.precision + }; + + for _i in decimal1.precision..precision { + f1 *= 10.0; + } + + for _i in 0..precision { + f1 *= 10.0; + } + + let mut f2 = decimal2.number as f32; + for _i in decimal2.precision..precision { + f2 *= 10.0; + } + + let number = (f1 / f2) as i64; + Decimal { number, precision } + } + + pub fn negate(decimal: Decimal) -> Decimal { + Decimal { + number: -decimal.number, + precision: decimal.precision, + } + } + + pub fn from_integer(from: i32) -> Decimal { + Decimal { + number: from as i64, + precision: 0, + } + } + + pub fn get_precision(&self) -> i32 { + self.precision + } + + pub fn get_number(&self) -> i64 { + self.number + } +} + +impl std::fmt::Display for Decimal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // This loses all of the benefit of having exact numeric types + // but it's what the C code does, so we have to follow it. + // In particular this copies the behavior of print_decimal in print.c. + // The C code has a different function called dectostr in decimal.c that + // does a proper string representation but it never gets called. + let mut temp = self.number as f64; + for _i in 0..self.precision { + temp /= 10.0; + } + + write!( + f, + "{:.precision$}", + temp, + precision = self.precision as usize + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decimal_creation() { + let decimal = Decimal::new(12345, 2).unwrap(); + assert_eq!(decimal.get_number(), 12345); + assert_eq!(decimal.get_precision(), 2); + } + + #[test] + fn test_parse_decimal() { + let decimal = Decimal::parse_decimal("123.45").unwrap(); + assert_eq!(decimal.get_number(), 12345); + assert_eq!(decimal.get_precision(), 2); + + let decimal = Decimal::parse_decimal("123").unwrap(); + assert_eq!(decimal.get_number(), 123); + assert_eq!(decimal.get_precision(), 0); + } + + #[test] + fn test_constants() { + assert_eq!(Decimal::ZERO.get_number(), 0); + assert_eq!(Decimal::ONE.get_number(), 100); + assert_eq!(Decimal::ONE_HUNDRED.get_number(), 10000); + } + + #[test] + fn test_arithmetic() { + let d1 = Decimal::new(100, 2).unwrap(); // 1.00 + let d2 = Decimal::new(50, 2).unwrap(); // 0.50 + + let sum = Decimal::add2(d1, d2); + assert_eq!(sum.get_number(), 150); // Buggy behavior: should be 150, not mathematically correct + + let diff = Decimal::subtract(d1, d2); + assert_eq!(diff.get_number(), 50); + } + + #[test] + fn test_display() { + let decimal = Decimal::new(12345, 2).unwrap(); + assert_eq!(format!("{}", decimal), "123.45"); + + let decimal = Decimal::new(123, 0).unwrap(); + assert_eq!(format!("{}", decimal), "123"); + } +} diff --git a/tpcdsgen/src/types/mod.rs b/tpcdsgen/src/types/mod.rs new file mode 100644 index 00000000..eb2481de --- /dev/null +++ b/tpcdsgen/src/types/mod.rs @@ -0,0 +1,13 @@ +pub mod address; +pub mod date; +pub mod decimal; +pub mod pricing; + +pub use address::Address; +pub use date::Date; +pub use decimal::Decimal; +pub use pricing::{ + generate_pricing_for_returns_table, generate_pricing_for_sales_table, + get_catalog_sales_pricing_limits, get_store_sales_pricing_limits, get_web_sales_pricing_limits, + Pricing, PricingLimits, +}; diff --git a/tpcdsgen/src/types/pricing.rs b/tpcdsgen/src/types/pricing.rs new file mode 100644 index 00000000..f1de8829 --- /dev/null +++ b/tpcdsgen/src/types/pricing.rs @@ -0,0 +1,505 @@ +use crate::random::{RandomNumberStream, RandomValueGenerator}; +use crate::types::Decimal; + +#[derive(Debug, Clone)] +pub struct Pricing { + wholesale_cost: Decimal, + list_price: Decimal, + sales_price: Decimal, + quantity: i32, + ext_discount_amount: Decimal, + ext_sales_price: Decimal, + ext_wholesale_cost: Decimal, + ext_list_price: Decimal, + tax_percent: Decimal, + ext_tax: Decimal, + coupon_amount: Decimal, + ship_cost: Decimal, + ext_ship_cost: Decimal, + net_paid: Decimal, + net_paid_including_tax: Decimal, + net_paid_including_shipping: Decimal, + net_paid_including_shipping_and_tax: Decimal, + net_profit: Decimal, + refunded_cash: Decimal, + reversed_charge: Decimal, + store_credit: Decimal, + fee: Decimal, + net_loss: Decimal, +} + +impl Pricing { + pub const QUANTITY_MIN: i32 = 1; + + // Predefined markup and discount minimums + pub fn markup_min() -> Decimal { + Decimal::new(0, 2).unwrap() + } + + pub fn discount_min() -> Decimal { + Decimal::new(0, 2).unwrap() + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + wholesale_cost: Decimal, + list_price: Decimal, + sales_price: Decimal, + quantity: i32, + ext_discount_amount: Decimal, + ext_sales_price: Decimal, + ext_wholesale_cost: Decimal, + ext_list_price: Decimal, + tax_percent: Decimal, + ext_tax: Decimal, + coupon_amount: Decimal, + ship_cost: Decimal, + ext_ship_cost: Decimal, + net_paid: Decimal, + net_paid_including_tax: Decimal, + net_paid_including_shipping: Decimal, + net_paid_including_shipping_and_tax: Decimal, + net_profit: Decimal, + refunded_cash: Decimal, + reversed_charge: Decimal, + store_credit: Decimal, + fee: Decimal, + net_loss: Decimal, + ) -> Self { + Pricing { + wholesale_cost, + list_price, + sales_price, + quantity, + ext_discount_amount, + ext_sales_price, + ext_wholesale_cost, + ext_list_price, + tax_percent, + ext_tax, + coupon_amount, + ship_cost, + ext_ship_cost, + net_paid, + net_paid_including_tax, + net_paid_including_shipping, + net_paid_including_shipping_and_tax, + net_profit, + refunded_cash, + reversed_charge, + store_credit, + fee, + net_loss, + } + } + + // Accessor methods + pub fn get_wholesale_cost(&self) -> Decimal { + self.wholesale_cost + } + + pub fn get_list_price(&self) -> Decimal { + self.list_price + } + + pub fn get_sales_price(&self) -> Decimal { + self.sales_price + } + + pub fn get_quantity(&self) -> i32 { + self.quantity + } + + pub fn get_ext_discount_amount(&self) -> Decimal { + self.ext_discount_amount + } + + pub fn get_ext_sales_price(&self) -> Decimal { + self.ext_sales_price + } + + pub fn get_ext_wholesale_cost(&self) -> Decimal { + self.ext_wholesale_cost + } + + pub fn get_ext_list_price(&self) -> Decimal { + self.ext_list_price + } + + pub fn get_tax_percent(&self) -> Decimal { + self.tax_percent + } + + pub fn get_ext_tax(&self) -> Decimal { + self.ext_tax + } + + pub fn get_coupon_amount(&self) -> Decimal { + self.coupon_amount + } + + pub fn get_ship_cost(&self) -> Decimal { + self.ship_cost + } + + pub fn get_ext_ship_cost(&self) -> Decimal { + self.ext_ship_cost + } + + pub fn get_net_paid(&self) -> Decimal { + self.net_paid + } + + pub fn get_net_paid_including_tax(&self) -> Decimal { + self.net_paid_including_tax + } + + pub fn get_net_paid_including_shipping(&self) -> Decimal { + self.net_paid_including_shipping + } + + pub fn get_net_paid_including_shipping_and_tax(&self) -> Decimal { + self.net_paid_including_shipping_and_tax + } + + pub fn get_net_profit(&self) -> Decimal { + self.net_profit + } + + pub fn get_refunded_cash(&self) -> Decimal { + self.refunded_cash + } + + pub fn get_reversed_charge(&self) -> Decimal { + self.reversed_charge + } + + pub fn get_store_credit(&self) -> Decimal { + self.store_credit + } + + pub fn get_fee(&self) -> Decimal { + self.fee + } + + pub fn get_net_loss(&self) -> Decimal { + self.net_loss + } +} + +// Limits structure for different pricing scenarios +#[derive(Debug, Clone)] +pub struct PricingLimits { + max_quantity_sold: i32, + max_markup: Decimal, + max_discount: Decimal, + max_wholesale_cost: Decimal, +} + +impl PricingLimits { + pub fn new( + max_quantity_sold: i32, + max_markup: Decimal, + max_discount: Decimal, + max_wholesale_cost: Decimal, + ) -> Self { + PricingLimits { + max_quantity_sold, + max_markup, + max_discount, + max_wholesale_cost, + } + } + + pub fn get_max_quantity_sold(&self) -> i32 { + self.max_quantity_sold + } + + pub fn get_max_markup(&self) -> Decimal { + self.max_markup + } + + pub fn get_max_discount(&self) -> Decimal { + self.max_discount + } + + pub fn get_max_wholesale_cost(&self) -> Decimal { + self.max_wholesale_cost + } +} + +/// Generate pricing for sales table (store_sales, catalog_sales, web_sales) +/// This is the Rust equivalent of Java's Pricing.generatePricingForSalesTable +pub fn generate_pricing_for_sales_table( + limits: &PricingLimits, + stream: &mut dyn RandomNumberStream, +) -> Pricing { + let quantity = RandomValueGenerator::generate_uniform_random_int( + Pricing::QUANTITY_MIN, + limits.get_max_quantity_sold(), + stream, + ); + let decimal_quantity = Decimal::from_integer(quantity); + let wholesale_cost = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::new(100, 2).unwrap(), // 1.00 + limits.get_max_wholesale_cost(), + stream, + ); + let ext_wholesale_cost = Decimal::multiply(decimal_quantity, wholesale_cost); + + let mut markup = RandomValueGenerator::generate_uniform_random_decimal( + Pricing::markup_min(), + limits.get_max_markup(), + stream, + ); + markup = Decimal::add2(markup, Decimal::ONE); + let list_price = Decimal::multiply(wholesale_cost, markup); + + let mut discount = Decimal::negate(RandomValueGenerator::generate_uniform_random_decimal( + Pricing::discount_min(), + limits.get_max_discount(), + stream, + )); + discount = Decimal::add2(discount, Decimal::ONE); + let sales_price = Decimal::multiply(list_price, discount); + let ext_list_price = Decimal::multiply(list_price, decimal_quantity); + let ext_sales_price = Decimal::multiply(sales_price, decimal_quantity); + let ext_discount_amount = Decimal::subtract(ext_list_price, ext_sales_price); + + let coupon = + RandomValueGenerator::generate_uniform_random_decimal(Decimal::ZERO, Decimal::ONE, stream); + let coupon_usage = RandomValueGenerator::generate_uniform_random_int(1, 100, stream); + let coupon_amount = if coupon_usage <= 20 { + // 20% of sales employ a coupon + Decimal::multiply(ext_sales_price, coupon) + } else { + Decimal::ZERO + }; + + let net_paid = Decimal::subtract(ext_sales_price, coupon_amount); + + let shipping = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::ZERO, + Decimal::ONE_HALF, + stream, + ); + let ship_cost = Decimal::multiply(list_price, shipping); + let ext_ship_cost = Decimal::multiply(ship_cost, decimal_quantity); + let net_paid_including_shipping = Decimal::add2(net_paid, ext_ship_cost); + let tax_percent = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::ZERO, + Decimal::NINE_PERCENT, + stream, + ); + let ext_tax = Decimal::multiply(net_paid, tax_percent); + let net_paid_including_tax = Decimal::add2(net_paid, ext_tax); + let net_paid_including_shipping_and_tax = Decimal::add2(net_paid_including_shipping, ext_tax); + let net_profit = Decimal::subtract(net_paid, ext_wholesale_cost); + + // only relevant for returns + let refunded_cash = Decimal::ZERO; + let reversed_charge = Decimal::ZERO; + let store_credit = Decimal::ZERO; + let fee = Decimal::ZERO; + let net_loss = Decimal::ZERO; + + Pricing::new( + wholesale_cost, + list_price, + sales_price, + quantity, + ext_discount_amount, + ext_sales_price, + ext_wholesale_cost, + ext_list_price, + tax_percent, + ext_tax, + coupon_amount, + ship_cost, + ext_ship_cost, + net_paid, + net_paid_including_tax, + net_paid_including_shipping, + net_paid_including_shipping_and_tax, + net_profit, + refunded_cash, + reversed_charge, + store_credit, + fee, + net_loss, + ) +} + +/// Predefined pricing limits for store_sales +pub fn get_store_sales_pricing_limits() -> PricingLimits { + PricingLimits::new(100, Decimal::ONE, Decimal::ONE, Decimal::ONE_HUNDRED) +} + +/// Predefined pricing limits for web_sales +pub fn get_web_sales_pricing_limits() -> PricingLimits { + PricingLimits::new( + 100, + Decimal::new(200, 2).unwrap(), // 2.00 + Decimal::ONE, + Decimal::ONE_HUNDRED, + ) +} + +/// Predefined pricing limits for catalog_sales +pub fn get_catalog_sales_pricing_limits() -> PricingLimits { + // CS_QUANTITY_MAX = 100, CS_MARKUP_MAX = 2.00, CS_DISCOUNT_MAX = 1.00, CS_WHOLESALE_MAX = 100.00 + PricingLimits::new( + 100, + Decimal::new(200, 2).unwrap(), // 2.00 + Decimal::ONE, + Decimal::ONE_HUNDRED, + ) +} + +/// Generate pricing for returns table (store_returns, catalog_returns, web_returns) +/// This is the Rust equivalent of Java's Pricing.generatePricingForReturnsTable +pub fn generate_pricing_for_returns_table( + stream: &mut dyn RandomNumberStream, + quantity: i32, + base_pricing: &Pricing, +) -> Pricing { + let wholesale_cost = base_pricing.get_wholesale_cost(); + let list_price = base_pricing.get_list_price(); + let sales_price = base_pricing.get_sales_price(); + let tax_percent = base_pricing.get_tax_percent(); + let ext_discount_amount = base_pricing.get_ext_discount_amount(); + let coupon_amount = base_pricing.get_coupon_amount(); + + let decimal_quantity = Decimal::from_integer(quantity); + let ext_wholesale_cost = Decimal::multiply(decimal_quantity, wholesale_cost); + let ext_list_price = Decimal::multiply(list_price, decimal_quantity); + let ext_sales_price = Decimal::multiply(sales_price, decimal_quantity); + let net_paid = ext_sales_price; + + let shipping = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::ZERO, + Decimal::ONE_HALF, + stream, + ); + let ship_cost = Decimal::multiply(list_price, shipping); + let ext_ship_cost = Decimal::multiply(ship_cost, decimal_quantity); + let net_paid_including_shipping = Decimal::add2(net_paid, ext_ship_cost); + let ext_tax = Decimal::multiply(net_paid, tax_percent); + let net_paid_including_tax = Decimal::add2(net_paid, ext_tax); + let net_paid_including_shipping_and_tax = Decimal::add2(net_paid_including_shipping, ext_tax); + let net_profit = Decimal::subtract(net_paid, ext_wholesale_cost); + + // See to it that the returned amounts add up to the total returned + // Allocate some of return to cash + let cash_percentage = Decimal::from_integer(RandomValueGenerator::generate_uniform_random_int( + 0, 100, stream, + )); + let refunded_cash = Decimal::multiply( + Decimal::divide(cash_percentage, Decimal::ONE_HUNDRED), + net_paid, + ); + + // Allocate some to reversed charges + let credit_percent = Decimal::from_integer(RandomValueGenerator::generate_uniform_random_int( + 1, 100, stream, + )); + let credit_percent = Decimal::divide(credit_percent, Decimal::ONE_HUNDRED); + let paid_minus_refunded = Decimal::subtract(net_paid, refunded_cash); + let reversed_charge = Decimal::multiply(credit_percent, paid_minus_refunded); + + // The rest is store credit + let store_credit = Decimal::subtract(net_paid, reversed_charge); + let store_credit = Decimal::subtract(store_credit, refunded_cash); + + // Pick a fee for the return + let fee = RandomValueGenerator::generate_uniform_random_decimal( + Decimal::ONE_HALF, + Decimal::ONE_HUNDRED, + stream, + ); + + // And calculate the net effect + let net_loss = Decimal::subtract(net_paid_including_shipping_and_tax, store_credit); + let net_loss = Decimal::subtract(net_loss, refunded_cash); + let net_loss = Decimal::subtract(net_loss, reversed_charge); + let net_loss = Decimal::add2(net_loss, fee); + + Pricing::new( + wholesale_cost, + list_price, + sales_price, + quantity, + ext_discount_amount, + ext_sales_price, + ext_wholesale_cost, + ext_list_price, + tax_percent, + ext_tax, + coupon_amount, + ship_cost, + ext_ship_cost, + net_paid, + net_paid_including_tax, + net_paid_including_shipping, + net_paid_including_shipping_and_tax, + net_profit, + refunded_cash, + reversed_charge, + store_credit, + fee, + net_loss, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pricing_creation() { + let pricing = Pricing::new( + Decimal::new(1000, 2).unwrap(), // wholesale_cost: 10.00 + Decimal::new(1500, 2).unwrap(), // list_price: 15.00 + Decimal::new(1200, 2).unwrap(), // sales_price: 12.00 + 5, // quantity + Decimal::new(300, 2).unwrap(), // ext_discount_amount: 3.00 + Decimal::new(6000, 2).unwrap(), // ext_sales_price: 60.00 + Decimal::new(5000, 2).unwrap(), // ext_wholesale_cost: 50.00 + Decimal::new(7500, 2).unwrap(), // ext_list_price: 75.00 + Decimal::new(8, 2).unwrap(), // tax_percent: 0.08 + Decimal::new(480, 2).unwrap(), // ext_tax: 4.80 + Decimal::new(100, 2).unwrap(), // coupon_amount: 1.00 + Decimal::new(200, 2).unwrap(), // ship_cost: 2.00 + Decimal::new(1000, 2).unwrap(), // ext_ship_cost: 10.00 + Decimal::new(5900, 2).unwrap(), // net_paid: 59.00 + Decimal::new(6380, 2).unwrap(), // net_paid_including_tax: 63.80 + Decimal::new(6900, 2).unwrap(), // net_paid_including_shipping: 69.00 + Decimal::new(7380, 2).unwrap(), // net_paid_including_shipping_and_tax: 73.80 + Decimal::new(900, 2).unwrap(), // net_profit: 9.00 + Decimal::ZERO, // refunded_cash + Decimal::ZERO, // reversed_charge + Decimal::ZERO, // store_credit + Decimal::ZERO, // fee + Decimal::ZERO, // net_loss + ); + + assert_eq!(pricing.get_quantity(), 5); + assert_eq!(pricing.get_wholesale_cost().get_number(), 1000); + assert_eq!(pricing.get_list_price().get_number(), 1500); + } + + #[test] + fn test_pricing_limits() { + let limits = PricingLimits::new(100, Decimal::ONE, Decimal::ONE, Decimal::ONE_HUNDRED); + + assert_eq!(limits.get_max_quantity_sold(), 100); + assert_eq!(limits.get_max_markup(), Decimal::ONE); + } + + #[test] + fn test_constants() { + assert_eq!(Pricing::QUANTITY_MIN, 1); + assert_eq!(Pricing::markup_min().get_number(), 0); + assert_eq!(Pricing::discount_min().get_number(), 0); + } +} diff --git a/tpcdsgen/tests/.gitkeep b/tpcdsgen/tests/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tpcdsgen/tests/fixtures/java/scale-1/MD5SUMS b/tpcdsgen/tests/fixtures/java/scale-1/MD5SUMS new file mode 100644 index 00000000..b38bb319 --- /dev/null +++ b/tpcdsgen/tests/fixtures/java/scale-1/MD5SUMS @@ -0,0 +1,25 @@ +cc9aabc63eb8603bd7330b6735ed0961 call_center.dat +0bbac1b8bdcf8ce2d5f0034980ee0196 catalog_page.dat +8460b5abd6b6ceaf6107f217b016fb23 catalog_returns.dat +51a0bc401b4b64d94736634b54068240 catalog_sales.dat +abac2e3925ab9bf66cec3b527a0468ed customer_address.dat +8831872c6d56ea9d4f24701f2feaef48 customer_demographics.dat +3672ffdefac3cf00413ecef71a753636 customer.dat +f3e77714328dcc57302777e72fd7747c date_dim.dat +a430da74c2e44926c53deb74e35b23f1 dbgen_version.dat +dccf2ff17c5e420021fbf92bf9a0a5ec household_demographics.dat +db8e8012be51ef81cf215774bec95533 income_band.dat +cfefc8724693ec9149f1d5b345fcecc2 inventory.dat +bebbcfd1acecdea16a5a3feb5e4deb96 item.dat +acb42558d0dc5e0ab6df5a664c1629cf promotion.dat +57fe9b8688095bd345cc846ec4400be0 reason.dat +791d16af982a67ad170a6b6527e25a35 ship_mode.dat +9009d804c02ee839e0b2ecd5fb4ae03f store_returns.dat +f003b3810e042d6dd47f48506616d88d store_sales.dat +80082d03e1b01340e19db3187d8edbd6 store.dat +a68339c5720d25380b53f6e0f2f72333 time_dim.dat +f56789e8b724b989d74e213e0686052f warehouse.dat +6feef91675c336d6f25e55ebbdf8c13c web_page.dat +e45390d32d1698fef71f05f474a4d748 web_returns.dat +15f9d835727f3a39a096c346f56e51f7 web_sales.dat +de5fb00a80673cb44b4b508da75d4bcf web_site.dat diff --git a/tpcdsgen/tests/fixtures/java/scale-10/MD5SUMS b/tpcdsgen/tests/fixtures/java/scale-10/MD5SUMS new file mode 100644 index 00000000..aae17a12 --- /dev/null +++ b/tpcdsgen/tests/fixtures/java/scale-10/MD5SUMS @@ -0,0 +1,25 @@ +235909679f4d125e769aa38eb16e9098 call_center.dat +a5daa0d93ecde8bd9f6ed79cd3b63916 catalog_page.dat +982a8b96fa0d9487015cd137136c8f68 catalog_returns.dat +97d5351b430d6c15e3906518315f0787 catalog_sales.dat +860602fea368111009ef08b167e1e299 customer_address.dat +8831872c6d56ea9d4f24701f2feaef48 customer_demographics.dat +486a030a55d468ef15ff2ff01583e6dc customer.dat +f3e77714328dcc57302777e72fd7747c date_dim.dat +97c49afb3b7d82f3ef9461effe915c2c dbgen_version.dat +dccf2ff17c5e420021fbf92bf9a0a5ec household_demographics.dat +db8e8012be51ef81cf215774bec95533 income_band.dat +4ad3640917c6567038f081bbe2cf0e3e inventory.dat +bff29691c74ae66eb2dcc3af686fb2ba item.dat +b8e8a7741f64edc5d09fdb0453c86705 promotion.dat +a1fdcd35ca0eddd0d5f37b0e5c2fddb3 reason.dat +791d16af982a67ad170a6b6527e25a35 ship_mode.dat +4ba001a6066db20066cd198242f92ca1 store_returns.dat +ecff92350fa0466e9b9407a1b5ad4020 store_sales.dat +430a01467a2d55d0e9a1bebad4f1c44b store.dat +a68339c5720d25380b53f6e0f2f72333 time_dim.dat +e0c56fe622774d09c9dec42029881ad5 warehouse.dat +e55695fdb2b86f96cf46e2a55b6f3748 web_page.dat +ac0197593d3f4cc3bb46c8ad7e6cd735 web_returns.dat +4da375300bcb0ce8785e1f100fb72efe web_sales.dat +4669d52e36cd112af10e137e5d8d7697 web_site.dat diff --git a/tpcdsgen/tests/fixtures/rust/scale-1/MD5SUMS b/tpcdsgen/tests/fixtures/rust/scale-1/MD5SUMS new file mode 100644 index 00000000..a9562be7 --- /dev/null +++ b/tpcdsgen/tests/fixtures/rust/scale-1/MD5SUMS @@ -0,0 +1,25 @@ +cc9aabc63eb8603bd7330b6735ed0961 call_center.dat +0bbac1b8bdcf8ce2d5f0034980ee0196 catalog_page.dat +8460b5abd6b6ceaf6107f217b016fb23 catalog_returns.dat +51a0bc401b4b64d94736634b54068240 catalog_sales.dat +abac2e3925ab9bf66cec3b527a0468ed customer_address.dat +8831872c6d56ea9d4f24701f2feaef48 customer_demographics.dat +3672ffdefac3cf00413ecef71a753636 customer.dat +f3e77714328dcc57302777e72fd7747c date_dim.dat +481e07e3e9084f2b4cdb1420220882b1 dbgen_version.dat +dccf2ff17c5e420021fbf92bf9a0a5ec household_demographics.dat +db8e8012be51ef81cf215774bec95533 income_band.dat +cfefc8724693ec9149f1d5b345fcecc2 inventory.dat +bebbcfd1acecdea16a5a3feb5e4deb96 item.dat +acb42558d0dc5e0ab6df5a664c1629cf promotion.dat +57fe9b8688095bd345cc846ec4400be0 reason.dat +791d16af982a67ad170a6b6527e25a35 ship_mode.dat +9009d804c02ee839e0b2ecd5fb4ae03f store_returns.dat +f003b3810e042d6dd47f48506616d88d store_sales.dat +80082d03e1b01340e19db3187d8edbd6 store.dat +a68339c5720d25380b53f6e0f2f72333 time_dim.dat +f56789e8b724b989d74e213e0686052f warehouse.dat +6feef91675c336d6f25e55ebbdf8c13c web_page.dat +e45390d32d1698fef71f05f474a4d748 web_returns.dat +15f9d835727f3a39a096c346f56e51f7 web_sales.dat +de5fb00a80673cb44b4b508da75d4bcf web_site.dat diff --git a/tpcdsgen/tests/fixtures/rust/scale-10/MD5SUMS b/tpcdsgen/tests/fixtures/rust/scale-10/MD5SUMS new file mode 100644 index 00000000..0f5aefb5 --- /dev/null +++ b/tpcdsgen/tests/fixtures/rust/scale-10/MD5SUMS @@ -0,0 +1,25 @@ +235909679f4d125e769aa38eb16e9098 call_center.dat +a5daa0d93ecde8bd9f6ed79cd3b63916 catalog_page.dat +982a8b96fa0d9487015cd137136c8f68 catalog_returns.dat +97d5351b430d6c15e3906518315f0787 catalog_sales.dat +860602fea368111009ef08b167e1e299 customer_address.dat +8831872c6d56ea9d4f24701f2feaef48 customer_demographics.dat +486a030a55d468ef15ff2ff01583e6dc customer.dat +f3e77714328dcc57302777e72fd7747c date_dim.dat +d56e94b0b433da6fe9f11d2001b73e1d dbgen_version.dat +dccf2ff17c5e420021fbf92bf9a0a5ec household_demographics.dat +db8e8012be51ef81cf215774bec95533 income_band.dat +4ad3640917c6567038f081bbe2cf0e3e inventory.dat +bff29691c74ae66eb2dcc3af686fb2ba item.dat +b8e8a7741f64edc5d09fdb0453c86705 promotion.dat +a1fdcd35ca0eddd0d5f37b0e5c2fddb3 reason.dat +791d16af982a67ad170a6b6527e25a35 ship_mode.dat +4ba001a6066db20066cd198242f92ca1 store_returns.dat +ecff92350fa0466e9b9407a1b5ad4020 store_sales.dat +430a01467a2d55d0e9a1bebad4f1c44b store.dat +a68339c5720d25380b53f6e0f2f72333 time_dim.dat +e0c56fe622774d09c9dec42029881ad5 warehouse.dat +e55695fdb2b86f96cf46e2a55b6f3748 web_page.dat +ac0197593d3f4cc3bb46c8ad7e6cd735 web_returns.dat +4da375300bcb0ce8785e1f100fb72efe web_sales.dat +4669d52e36cd112af10e137e5d8d7697 web_site.dat diff --git a/tpchgen/README.md b/tpchgen/README.md index 5d0033e7..abe79d8d 100644 --- a/tpchgen/README.md +++ b/tpchgen/README.md @@ -1,4 +1,4 @@ -# TPC-H Data Generator Crate +# TPC-H Data Generator Crate This crate provides the core data generator logic for TPC-H. It has no dependencies and is easy to embed in any other Rust projects.