Skip to content

SnowCLI is overly aggressive in determining template characters like &. #2714

Description

@sfc-gh-myoung

SnowCLI version

3.13.1

Python version

3.14.2 and 3.11.2

Platform

macOS-15.7.2-arm64-arm-64bit-Mach-O

What happened

SnowCLI is overly aggressive in detecting template characters like &. I've seen this happen multiple times when & is embedded in a DDL COMMENT or in a Semantic View synonym.

The issue seems to happen when there are no spaces around &. For example Principal & Interest within a comment or synonym won't trigger the issue. However, Principal&Interest will trigger it.

I've included the full DDL below, however this is the section with the problematic behavior:

FACTS (
    PAYMENT.scheduled_payment AS scheduled_payment
      WITH SYNONYMS ('monthly payment', 'principal and interest payment', 'scheduled amount', 'Principal&Interest payment')
      COMMENT = 'Scheduled monthly payment amount with principal&interest',

This is the full Semantic View DDL where both the comment and synonym have the &:

  TABLES (
    PAYMENT AS FREEDOM_MORTGAGE_DEMO.LOAN_SERVICING.FACT_LOAN_PAYMENTS
      PRIMARY KEY (payment_id)
      WITH SYNONYMS ('payments', 'payment transactions', 'payment history')
      COMMENT = 'Monthly payment transactions',
    
    ORIG AS FREEDOM_MORTGAGE_DEMO.LOAN_ORIGINATION.FACT_LOAN_ORIGINATIONS
      PRIMARY KEY (loan_id)
      WITH SYNONYMS ('loans', 'originations')
      COMMENT = 'Loan origination details',
    
    BORROWER AS FREEDOM_MORTGAGE_DEMO.LOAN_ORIGINATION.DIM_BORROWER
      PRIMARY KEY (borrower_id)
      WITH SYNONYMS ('borrowers', 'customers')
      COMMENT = 'Borrower information',
    
    DELINQUENCY AS FREEDOM_MORTGAGE_DEMO.LOAN_SERVICING.FACT_DELINQUENCIES
      PRIMARY KEY (delinquency_id)
      WITH SYNONYMS ('delinquencies', 'past due events')
      COMMENT = 'Delinquency events'
  )
  RELATIONSHIPS (
    PAYMENT_TO_ORIG AS PAYMENT(loan_id) REFERENCES ORIG(loan_id),
    PAYMENT_TO_BORROWER AS PAYMENT(borrower_id) REFERENCES BORROWER(borrower_id),
    DELINQ_TO_PAYMENT AS DELINQUENCY(payment_id) REFERENCES PAYMENT(payment_id)
  )
  FACTS (
    PAYMENT.scheduled_payment AS scheduled_payment
      WITH SYNONYMS ('monthly payment', 'principal and interest payment', 'scheduled amount', 'Principal&Interest payment')
      COMMENT = 'Scheduled monthly payment amount with principal&interest',
    
    PAYMENT.actual_payment AS actual_payment
      WITH SYNONYMS ('amount paid', 'payment received', 'paid amount')
      COMMENT = 'Actual amount paid by borrower',
    
    PAYMENT.principal_paid AS principal_paid
      WITH SYNONYMS ('principal payment', 'principal portion')
      COMMENT = 'Principal portion of payment',
    
    PAYMENT.interest_paid AS interest_paid
      WITH SYNONYMS ('interest payment', 'interest portion')
      COMMENT = 'Interest portion of payment',
    
    PAYMENT.escrow_paid AS escrow_paid
      WITH SYNONYMS ('escrow payment', 'escrow portion', 'taxes and insurance')
      COMMENT = 'Escrow portion (taxes and insurance)',
    
    PAYMENT.late_fee AS late_fee
      WITH SYNONYMS ('late charge', 'penalty', 'late payment fee')
      COMMENT = 'Late fee charged on payment',
    
    PAYMENT.remaining_balance AS remaining_balance
      WITH SYNONYMS ('outstanding balance', 'principal balance', 'balance after payment')
      COMMENT = 'Remaining loan balance after payment',
    
    DELINQUENCY.days_past_due AS days_past_due
      WITH SYNONYMS ('DPD', 'days delinquent', 'past due days')
      COMMENT = 'Number of days payment is past due',
    
    DELINQUENCY.outstanding_balance AS outstanding_balance
      WITH SYNONYMS ('past due balance', 'arrears balance', 'delinquent balance')
      COMMENT = 'Outstanding balance in delinquency',
    
    DELINQUENCY.late_charges AS late_charges
      WITH SYNONYMS ('accumulated late fees', 'late fee total')
      COMMENT = 'Total late charges accumulated',
    
    DELINQUENCY.total_amount_due AS total_amount_due
      WITH SYNONYMS ('total past due', 'total arrears', 'amount owed', 'total delinquent amount')
      COMMENT = 'Total amount due including principal, interest, and fees',
    
    ORIG.funded_amount AS funded_amount
      WITH SYNONYMS ('original balance', 'original loan amount', 'initial principal')
      COMMENT = 'Original funded loan amount'
  )
  DIMENSIONS (
    PAYMENT.payment_id AS payment_id
      WITH SYNONYMS ('transaction ID', 'payment number')
      COMMENT = 'Unique payment transaction identifier',
    
    PAYMENT.loan_id AS loan_id
      WITH SYNONYMS ('loan number', 'loan ID')
      COMMENT = 'Loan identifier',
    
    PAYMENT.borrower_id AS borrower_id
      WITH SYNONYMS ('customer ID', 'borrower number')
      COMMENT = 'Borrower identifier',
    
    PAYMENT.payment_number AS payment_number
      WITH SYNONYMS ('payment sequence', 'payment count')
      COMMENT = 'Sequential payment number (1, 2, 3...)',
    
    PAYMENT.payment_due_date AS payment_due_date
      WITH SYNONYMS ('due date', 'scheduled date', 'payment date')
      COMMENT = 'Date payment was due',
    
    PAYMENT.payment_date AS payment_date
      WITH SYNONYMS ('paid date', 'received date', 'actual payment date')
      COMMENT = 'Date payment was actually received',
    
    PAYMENT.payment_status AS payment_status
      WITH SYNONYMS ('status', 'payment state', 'timeliness')
      COMMENT = 'Payment status (On Time, Late 1-30 days, etc)',
    
    ORIG.loan_type AS loan_type
      WITH SYNONYMS ('product type', 'mortgage type')
      COMMENT = 'Loan product type (Conventional, FHA, VA, Jumbo)',
    
    ORIG.loan_program AS loan_program
      WITH SYNONYMS ('program', 'loan product')
      COMMENT = 'Loan program (30-Year Fixed, ARM, etc)',
    
    ORIG.loan_status AS loan_status
      WITH SYNONYMS ('status', 'current status')
      COMMENT = 'Current loan status (Active, Paid Off, etc)',
    
    ORIG.funding_date AS funding_date
      WITH SYNONYMS ('origination date', 'closing date', 'start date')
      COMMENT = 'Date loan was funded',
    
    BORROWER.state AS state
      WITH SYNONYMS ('borrower state', 'borrower location', 'customer state')
      COMMENT = 'Borrower state (2-letter code)',
    
    BORROWER.city AS city
      WITH SYNONYMS ('borrower city', 'city name', 'customer city')
      COMMENT = 'Borrower city name',
    
    DELINQUENCY.delinquency_id AS delinquency_id
      COMMENT = 'Unique delinquency event identifier'
  )
  METRICS (
    PAYMENT.payment_count AS COUNT(DISTINCT payment_id)
      WITH SYNONYMS ('total payments', 'number of payments', 'payment volume')
      COMMENT = 'Total number of payment transactions',
    
    PAYMENT.on_time_payment_count AS COUNT(DISTINCT CASE WHEN payment_status = 'On Time' THEN payment_id END)
      WITH SYNONYMS ('on-time payments', 'current payments', 'timely payments')
      COMMENT = 'Number of on-time payments',
    
    PAYMENT.late_payment_count AS COUNT(DISTINCT CASE WHEN payment_status != 'On Time' THEN payment_id END)
      WITH SYNONYMS ('late payments', 'delinquent payments', 'past due payments')
      COMMENT = 'Number of late payments',
    
    DELINQUENCY.delinquency_event_count AS COUNT(DISTINCT delinquency_id)
      WITH SYNONYMS ('delinquency count', 'number of delinquencies')
      COMMENT = 'Number of delinquency events',
    
    PAYMENT.total_scheduled AS SUM(scheduled_payment)
      WITH SYNONYMS ('total scheduled payments', 'scheduled amount')
      COMMENT = 'Total scheduled payment amount',
    
    PAYMENT.total_actual AS SUM(actual_payment)
      WITH SYNONYMS ('total payments received', 'collected amount')
      COMMENT = 'Total actual payments received',
    
    PAYMENT.total_principal_paid AS SUM(principal_paid)
      WITH SYNONYMS ('total principal', 'principal collected')
      COMMENT = 'Total principal payments collected',
    
    PAYMENT.total_interest_paid AS SUM(interest_paid)
      WITH SYNONYMS ('total interest', 'interest income', 'interest collected')
      COMMENT = 'Total interest payments collected',
    
    PAYMENT.total_escrow_paid AS SUM(escrow_paid)
      WITH SYNONYMS ('total escrow', 'escrow collected')
      COMMENT = 'Total escrow payments collected',
    
    PAYMENT.total_late_fees AS SUM(late_fee)
      WITH SYNONYMS ('late fees collected', 'penalty income')
      COMMENT = 'Total late fees charged',
    
    DELINQUENCY.total_delinquent_balance AS SUM(total_amount_due)
      WITH SYNONYMS ('total past due', 'total arrears')
      COMMENT = 'Total amount past due across all delinquencies',
    
    PAYMENT.avg_scheduled_payment AS AVG(scheduled_payment)
      WITH SYNONYMS ('average payment', 'mean monthly payment')
      COMMENT = 'Average scheduled payment amount',
    
    PAYMENT.avg_remaining_balance AS AVG(remaining_balance)
      WITH SYNONYMS ('average balance', 'mean outstanding balance')
      COMMENT = 'Average remaining loan balance',
    
    DELINQUENCY.avg_days_past_due AS AVG(days_past_due)
      WITH SYNONYMS ('average DPD', 'mean delinquency days')
      COMMENT = 'Average days past due for delinquent loans'
  )
  COMMENT = 'Loan servicing analytics for Cortex Analyst: payment performance, delinquency tracking, and portfolio health metrics'
  COPY GRANTS;

Console output

I've tested with snowcli installed via `homebrew` and via `uvx`.

When `&` is in the synonym of a semantic view:

% uvx --from snowflake-cli snow sql -f 08__create_semantic_view_servicing.sql
Warning: &{ ... } syntax is deprecated and will no longer be supported. Use <% ... %> syntax instead.
SQL template rendering error: 'Interest' is undefined
╭─ Error ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ SQL rendering error                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

When `&` is in a comment:

% uvx --from snowflake-cli snow sql -f 08__create_semantic_view_servicing.sql
Warning: &{ ... } syntax is deprecated and will no longer be supported. Use <% ... %> syntax instead.
SQL template rendering error: 'interest' is undefined

How to reproduce

Create a DDL statement with & in a comment or synonym where there is no space around the &. Then change the DDL to use a space & and you'll find the error goes away.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions