<commit_before>import { validateSqlAST, inspect } from './util'

export default function stringifySqlAST(topNode, context) {
  validateSqlAST(topNode)
  let { selections, joins, wheres } = _stringifySqlAST(null, topNode, '', context, [], [], [])
  // make sure these are unique by converting to a set and then back to an array
  selections = [ ...new Set(selections) ]
  if (!selections.length) return ''
  return 'SELECT\n  ' + selections.join(',\n  ') + '\n' + joins.join('\n') + '\n' + wheres.join('\n')
}

function _stringifySqlAST(parent, node, prefix, context, selections, joins, wheres) {
  switch(node.type) {
  case 'table':
    // generate the "where" condition, if applicable
    if (node.where) {
      const whereCondition = node.where(`"${node.as}"`, node.args || {}, context) 
      if (whereCondition) {
        wheres.push(`WHERE ${whereCondition}`)
      }
    }

    // generate the join or joins
    // this condition is for single joins (one-to-one or one-to-many relations)
    if (node.sqlJoin) {
      const joinCondition = node.sqlJoin(`"${parent.as}"`, `"${node.as}"`)

      joins.push(
        `LEFT JOIN "${node.name}" AS "${node.as}" ON ${joinCondition}`
      )
    // this condition is through a join table (many-to-many relations)
    } else if (node.joinTable) {
      if (!node.sqlJoins) throw new Error('Must set "sqlJoins" for a join table.')
      const joinCondition1 = node.sqlJoins[0](`"${parent.as}"`, `"${node.joinTableAs}"`)
      const joinCondition2 = node.sqlJoins[1](`"${node.joinTableAs}"`, `"${node.as}"`)

      joins.push(
        `LEFT JOIN "${node.joinTable}" AS "${node.joinTableAs}" ON ${joinCondition1}`,
        `LEFT JOIN "${node.name}" AS "${node.as}" ON ${joinCondition2}`
      )
    } else {
      // otherwise, this table is not being joined, its the first one and it goes in the "FROM" clause
      joins.push(
        `FROM "${node.name}" AS "${node.fieldName}"`
      )
    }

    // recurse thru nodes
    for (let child of node.children) {
      _stringifySqlAST(node, child, parent ? prefix + node.as + '__' : prefix, context, selections, joins, wheres)
    }

    break
  case 'column':
    selections.push(
      `"${parent.as}"."${node.name}" AS "${prefix + node.name}"`
    )
    break
  case 'columnDeps':
    node.name.forEach(col => selections.push(
      `"${parent.as}"."${col}" AS "${prefix + col}"`
    ))
    break
  case 'composite':
    const keys = node.name.map(key => `"${parent.as}"."${key}"`)
    selections.push(
      `${keys.join(' || ')} AS "${prefix + node.fieldName}"`
    )
    break
  case 'noop':<commit_msg>Permit use schema name and table name, like public.person and stock.products, using double quotes "", like this "public.person", in sql is a identifier<commit_after>import { validateSqlAST, inspect } from './util'

export default function stringifySqlAST(topNode, context) {
  validateSqlAST(topNode)
  let { selections, joins, wheres } = _stringifySqlAST(null, topNode, '', context, [], [], [])
  // make sure these are unique by converting to a set and then back to an array
  selections = [ ...new Set(selections) ]
  if (!selections.length) return ''
  return 'SELECT\n  ' + selections.join(',\n  ') + '\n' + joins.join('\n') + '\n' + wheres.join('\n')
}

function _stringifySqlAST(parent, node, prefix, context, selections, joins, wheres) {
  switch(node.type) {
  case 'table':
    // generate the "where" condition, if applicable
    if (node.where) {
      const whereCondition = node.where(`"${node.as}"`, node.args || {}, context) 
      if (whereCondition) {
        wheres.push(`WHERE ${whereCondition}`)
      }
    }

    // generate the join or joins
    // this condition is for single joins (one-to-one or one-to-many relations)
    if (node.sqlJoin) {
      const joinCondition = node.sqlJoin(`"${parent.as}"`, `"${node.as}"`)

      joins.push(
        `LEFT JOIN ${node.name} AS "${node.as}" ON ${joinCondition}`
      )
    // this condition is through a join table (many-to-many relations)
    } else if (node.joinTable) {
      if (!node.sqlJoins) throw new Error('Must set "sqlJoins" for a join table.')
      const joinCondition1 = node.sqlJoins[0](`"${parent.as}"`, `"${node.joinTableAs}"`)
      const joinCondition2 = node.sqlJoins[1](`"${node.joinTableAs}"`, `"${node.as}"`)

      joins.push(
        `LEFT JOIN ${node.joinTable} AS "${node.joinTableAs}" ON ${joinCondition1}`,
        `LEFT JOIN ${node.name} AS "${node.as}" ON ${joinCondition2}`
      )
    } else {
      // otherwise, this table is not being joined, its the first one and it goes in the "FROM" clause
      joins.push(
        `FROM ${node.name} AS "${node.fieldName}"`
      )
    }

    // recurse thru nodes
    for (let child of node.children) {
      _stringifySqlAST(node, child, parent ? prefix + node.as + '__' : prefix, context, selections, joins, wheres)
    }

    break
  case 'column':
    selections.push(
      `"${parent.as}"."${node.name}" AS "${prefix + node.name}"`
    )
    break
  case 'columnDeps':
    node.name.forEach(col => selections.push(
      `"${parent.as}"."${col}" AS "${prefix + col}"`
    ))
    break
  case 'composite':
    const keys = node.name.map(key => `"${parent.as}"."${key}"`)
    selections.push(
      `${keys.join(' || ')} AS "${prefix + node.fieldName}"`
    )
    break
  case 'noop':
{'fraction_non_alphanumeric': 0.17353051291651067, 'fraction_numerical': 0.0022463496817671283, 'mean_word_length': 3.0112612612612613, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2449811', 'n_tokens_mistral': 1748, 'n_tokens_neox': 1707, 'n_words': 468}