D1 restapi param binding alway 400, but concat work

For Workers & Pages, what is the name of the domain?

What is the issue or error you’re encountering

400

What are the steps to reproduce the issue?

def save_initial_app_profile_concat(app_data):
    """
    Save basic app profile data from Sitemap to the D1 database
    using direct string concatenation (SQL INJECTION RISK).
    """
    if not app_data:
        return

    query_url = f"{CLOUDFLARE_BASE_URL}/query"
    headers = {
        "Authorization": f"Bearer {CLOUDFLARE_API_TOKEN}",
        "Content-Type": "application/json"
    }

    # Generate row hash using lastmodify
    row_hash = calculate_row_hash(app_data["url"], app_data["lastmodify"])
    url = app_data["url"].replace('https://', '')

    # Extract appid and appname from URL structure
    app_data["appid"] = url.split('/')[-1]
    app_data["appname"] = url.split('/')[-2]
    app_data["country"] = url.split('/')[-4]
    current_time = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
    app_data["updated_at"] = current_time

    # Handle the None values
    app_data['releasedate'] = str(app_data.get('releasedate',''))
    app_data['version'] = str(app_data.get('version','')).strip()
    app_data['seller'] = str(app_data.get('seller','')).strip()
    app_data['size'] = str(app_data.get('size','')).strip()
    app_data['category'] = str(app_data.get('category','')).strip()
    app_data['lang'] = str(app_data.get('lang','')).strip()
    app_data['age'] = str(app_data.get('age','')).strip()
    app_data['copyright'] = str(app_data.get('copyright','')).strip()
    app_data['pricetype'] = str(app_data.get('pricetype','')).strip()
    app_data['priceplan'] = str(app_data.get('priceplan','')).strip()
    app_data['ratings'] = str(app_data.get('ratings',''))
    app_data['reviewcount'] = str(app_data.get('reviewcount',''))
    app_data['website'] = str(app_data.get('website',''))
    
    # SQL Query to insert basic app profile with direct values (SQL INJECTION RISK)
    sql_query = f"""
    INSERT OR IGNORE INTO ios_app_profiles (
        appid, appname, country, url, releasedate,
        version, seller, size, category, lang, 
        age, copyright, pricetype, priceplan, ratings,
        reviewcount,updated_at, website, lastmodify, row_hash
    ) VALUES (
        '{escape_sql(app_data.get("appid"))}', '{escape_sql(app_data.get("appname"))}', '{escape_sql(app_data.get("country"))}', '{escape_sql(app_data.get("url"))}', '{escape_sql(app_data.get("releasedate"))}',
        '{escape_sql(app_data.get("version"))}', '{escape_sql(app_data.get("seller"))}', '{escape_sql(app_data.get("size"))}', '{escape_sql(app_data.get("category"))}', '{escape_sql(app_data.get("lang"))}',
        '{escape_sql(app_data.get("age"))}', '{escape_sql(app_data.get("copyright"))}', '{escape_sql(app_data.get("pricetype"))}', '{escape_sql(app_data.get("priceplan"))}', '{escape_sql(app_data.get("ratings"))}',
         '{escape_sql(app_data.get("reviewcount"))}', '{escape_sql(app_data.get("updated_at",current_time))}', '{escape_sql(app_data.get("website"))}', '{escape_sql(app_data.get("lastmodify", current_time))}', '{escape_sql(row_hash)}'
    )
    """
    
    payload = {
        "sql": sql_query
    }
    print("Payload:", payload)
    print("Headers:",headers)
    try:
         with httpx.Client() as client:
            response = client.post(query_url, headers=headers, json=payload)
            response.raise_for_status()
            logging.info(f"Saved basic app profile for {app_data['appname']} ({app_data['appid']}).")
    except httpx.RequestError as e:
        logging.error(f"Failed to save basic app profile: {e}\n{response.json()}\n {payload}")
def save_initial_app_profile_param(app_data):
    """
    Save basic app profile data from Sitemap to the D1 database
    using parameterized queries.
    """
    if not app_data:
        return

    query_url = f"{CLOUDFLARE_BASE_URL}/query"
    headers = {
        "Authorization": f"Bearer {CLOUDFLARE_API_TOKEN}",
        "Content-Type": "application/json"
    }

    # Generate row hash using lastmodify
    row_hash = calculate_row_hash(app_data["url"], app_data["lastmodify"])
    url = app_data["url"].replace('https://', '')

    # Extract appid and appname from URL structure
    app_data["appid"] = url.split('/')[-1]
    app_data["appname"] = url.split('/')[-2]
    app_data["country"] = url.split('/')[-4]
    current_time = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
    app_data["updated_at"] = current_time

    # Handle the None values
    app_data['releasedate'] = str(app_data.get('releasedate',''))
    app_data['version'] = str(app_data.get('version','')).strip()
    app_data['seller'] = str(app_data.get('seller','')).strip()
    app_data['size'] = str(app_data.get('size','')).strip()
    app_data['category'] = str(app_data.get('category','')).strip()
    app_data['lang'] = str(app_data.get('lang','')).strip()
    app_data['age'] = str(app_data.get('age','')).strip()
    app_data['copyright'] = str(app_data.get('copyright','')).strip()
    app_data['pricetype'] = str(app_data.get('pricetype','')).strip()
    app_data['priceplan'] = str(app_data.get('priceplan','')).strip()
    app_data['ratings'] = str(app_data.get('ratings',''))
    app_data['reviewcount'] = str(app_data.get('reviewcount',''))
    app_data['website'] = str(app_data.get('website',''))
    
    # SQL Query to insert basic app profile with IGNORE and placeholders
    sql_query = """
    INSERT OR IGNORE INTO ios_app_profiles (
        appid, appname, country, url, releasedate,
        version, seller, size, category, lang, 
        age, copyright, pricetype, priceplan, ratings,
        reviewcount,updated_at, website, lastmodify, row_hash
    ) VALUES (?, ?, ?, ?, ?,?, ?, ?, ?, ?,?, ?, ?, ?, ?,?, ?, ?,?,?)
    """

    # Prepare values for the parameterized query
    values = (
        str(app_data.get("appid")),
        str(app_data.get("appname")),
        str(app_data.get("country")),
        str(app_data.get("url")),
        str(app_data.get("releasedate")),
        str(app_data.get("version")),
        str(app_data.get("seller")),
        str(app_data.get("size")),
        str(app_data.get("category")),
        str(app_data.get("lang")),
        str(app_data.get("age")),
        str(app_data.get("copyright")),
        str(app_data.get("pricetype")),
        str(app_data.get("priceplan")),
        str(app_data.get("ratings")),
        str(app_data.get("reviewcount")),
        str(app_data.get("updated_at",current_time)),
        str(app_data.get("website")),
        str(app_data.get("lastmodify", current_time)),
        str(row_hash))
    payload = {
        "sql": sql_query,
        "bindings": values
    }
    print("Payload:", payload)
    print("Headers:",headers)
    try:
         with httpx.Client() as client:
            response = client.post(query_url, headers=headers, json=payload)
            response.raise_for_status()
            logging.info(f"Saved basic app profile for {app_data['appname']} ({app_data['appid']}).")
    except httpx.RequestError as e:
        logging.error(f"Failed to save basic app profile: {e}\n{response.json()}\n {payload}")

when use binding way, consistently give me

2025-01-10 08:55:33,605 - ERROR - Error processing initial app profile id1357464684: Client error ‘400 Bad Request’ for url ‘https://api.cloudflare.com/client/v4/accounts/***/d1/database/***/query