  The cause is that the plugin as_sql.py was written for EasyCoder some while ago and was never
  brought up to date. It relies too much on dictionary values that no longer exist, having been
  replaced by attributes, checks on variaable types etc.
  Please take a look and see if it can be brought up to date sufficiently to be able to compile and
  run the test script.

  Searched for 17 patterns, read 9 files, listed 3 directories (ctrl+o to expand)

● I have enough understanding. The plugin has three main issues:
  1. record['hasValue'] — that key is no longer set on symbol records (only keyword, name, object,
  etc.)
  2. record['value'][record['index']]['keys'] — old symbol-record structure that doesn't exist;
  values now live on the ECVariable object, not the record
  3. vartype = str (the class) followed by vartype.upper() — TypeError at runtime (str.upper() needs
  an instance)

  Plus smaller things: k_table passing False as classname (should be a class name string); if/elif
  chain bug in key-flag parsing.

  Refactor: stash table configs in a plugin-level dict keyed by target name, check keyword ==
  'variable' instead of hasValue, and fix the type map.

● Write(allspeak-py/plugins/as_sql.py)
  ⎿  Added 81 lines, removed 67 lines
       1 -from allspeak import Handler, ECValue
       1 +from allspeak import Handler, ECValue, RuntimeError
       2
       3  class SQL(Handler):
       4
       5      def __init__(self, compiler):
       6          Handler.__init__(self, compiler)
       7 +        # target symbol name -> {'tableName': str, 'keys': list}
       8 +        self.tables = {}
       9
      10      def getName(self):
      11          return 'sql'
     ...
      13      #############################################################################
      14      # Keyword handlers
      15
      14 -    # create {table} {name} [with ...]
      15 -    # {name} {flag(s)} {type} [default {value}] [and ..]
      16 +    # table {name}
      17 +    def k_table(self, command):
      18 +        return self.compileVariable(command, 'ECVariable')
      19 +
      20 +    def r_table(self, command):
      21 +        return self.nextPC()
      22 +
      23 +    # create {table} {name} [key/include ...]
      24 +    # {name} {flag(s)} {type} [default {value}] [check {expr}]
      25      def k_create(self, command):
      26          if self.nextIsSymbol():
      27              record = self.getSymbolRecord()
      19 -            if record['keyword'] == 'table':
      28 +            if record.get('keyword') == 'table':
      29                  command['target'] = record['name']
      30                  command['tableName'] = self.nextValue()
      31                  keys = []
     ...
      40                          if token == 'primary':
      41                              item['primary'] = True
      42                              self.nextToken()
      34 -                        if token == 'secondary':
      43 +                        elif token == 'secondary':
      44                              item['secondary'] = True
      45                              self.nextToken()
      46                          elif token == 'required':
     ...
       63          return False
       64
       65      def r_create(self, command):
       57 -        record = self.getVariable(command['target'])
       58 -        self.putSymbolValue(record, command)
       66 +        self.tables[command['target']] = {
       67 +            'tableName': self.textify(command['tableName']),
       68 +            'keys': command['keys'],
       69 +        }
       70          return self.nextPC()
       71
       72 +    # get {variable} from {table} [as {form}]
       73      def k_get(self, command):
       74          if self.nextIsSymbol():
       75              record = self.getSymbolRecord()
       64 -            if record['hasValue']:
       76 +            if record.get('keyword') == 'variable':
       77                  command['target'] = record['name']
       78                  self.skip('from')
       79                  if self.nextIsSymbol():
       80                      record = self.getSymbolRecord()
       69 -                    if record['keyword'] == 'table':
       81 +                    if record.get('keyword') == 'table':
       82                          command['entity'] = record['name']
       83                          if self.peek() == 'as':
       84                              self.nextToken()
       85                              command['form'] = self.nextToken()
       74 -                        else: command['form'] = 'sql'
       86 +                        else:
       87 +                            command['form'] = 'sql'
       88                          self.add(command)
       89                  return True
       90          return False
       91
       92 +    # Mapping from AllSpeak table column types to SQL type literals
       93 +    TYPE_MAP = {
       94 +        'string':   'VARCHAR(255)',
       95 +        'text':     'TEXT',
       96 +        'u64':      'BIGINT',
       97 +        'datetime': 'TIMESTAMPTZ',
       98 +        'uuid':     'UUID',
       99 +    }
      100 +
      101      def r_get(self, command):
       80 -        target = self.getVariable(command['target'])
       81 -        entity = self.getVariable(command['entity'])
      102 +        target_record = self.getVariable(command['target'])
      103 +        entity_name = command['entity']
      104          form = command['form']
       83 -        keyword = entity['keyword']
       84 -        if keyword == 'table':
       85 -            value = self.getSymbolValue(entity)
       86 -            tableName = self.textify(value['tableName'])
       87 -            output = []
       88 -            if form == 'sql':
       89 -                # -------------------------------------------------------------
       90 -                # Here are the rules for generating SQL
       91 -                output.append(f'DROP TABLE IF EXISTS {tableName} CASCADE;')
       92 -                output.append(f'CREATE TABLE {tableName} {{')
       93 -                secondary = False
       94 -                includes = []
       95 -                keys = entity['value'][entity['index']]['keys']
       96 -                for index, key in enumerate(keys):
       97 -                    item = []
       98 -                    if 'include' in key:
       99 -                        name = self.textify(key['include'])
      100 -                        includes.append(f'{name}_id')
      101 -                        item = f'{name}_id BIGINT REFERENCES {name}'
      102 -                    else:
      103 -                        if 'secondary' in key:
      104 -                            secondary = True
      105 -                            output.append('  id BIGSERIAL PRIMARY KEY,')
      106 -                        item.append(self.textify(key['name']))
      107 -                        vartype = key['type']
      108 -                        if vartype == 'string': vartype = str
      109 -                        elif vartype == 'datetime': vartype = 'timestamptz'
      110 -                        elif vartype == 'u64': vartype = 'bigint'
      111 -                        item.append(vartype.upper())
      112 -                        if secondary:
      113 -                            item.append('UNIQUE NOT NULL')
      114 -                            secondary = False
      115 -                        if 'primary' in key: item.append('PRIMARY KEY')
      116 -                        if 'required' in key: item.append('NOT NULL')
      117 -                        if 'default' in key:
      118 -                            default = self.textify(key['default'])
      119 -                            item.append(f'DEFAULT \'{default}\'')
      120 -                        if 'check' in key:
      121 -                            check = self.textify(key['check'])
      122 -                            item.append(f'CHECK ({check})')
      123 -                        item = ' '.join(item)
      124 -                    if index < len(keys) - 1 or len(includes) > 0: item = f'{item},
          -'
      125 -                    output.append(f'  {item}')
      126 -                if len(includes) > 0:
      127 -                    includes = ', '.join(includes)
      128 -                    item = f'  PRIMARY KEY ({includes})'
      129 -                    output.append(item)
      130 -                output.append('};')
      131 -                # -------------------------------------------------------------
      132 -            v = ECValue(domain='sql', type=str, content='\n'.join(output))
      133 -            self.putSymbolValue(target, v)
      134 -        return self.nextPC()
      105 +        config = self.tables.get(entity_name)
      106 +        if config is None:
      107 +            raise RuntimeError(self.program, f'Table "{entity_name}" has not been d
          +efined with "create"')
      108
      136 -    def k_table(self, command):
      137 -        return self.compileVariable(command, False)
      109 +        output = []
      110 +        if form == 'sql':
      111 +            # -----------------------------------------------------------------
      112 +            # Rules for generating SQL
      113 +            table_name = config['tableName']
      114 +            keys = config['keys']
      115 +            output.append(f'DROP TABLE IF EXISTS {table_name} CASCADE;')
      116 +            output.append(f'CREATE TABLE {table_name} {{')
      117 +            includes = []
      118 +            for index, key in enumerate(keys):
      119 +                if 'include' in key:
      120 +                    name = self.textify(key['include'])
      121 +                    includes.append(f'{name}_id')
      122 +                    item = f'{name}_id BIGINT REFERENCES {name}'
      123 +                else:
      124 +                    parts = []
      125 +                    if 'secondary' in key:
      126 +                        output.append('  id BIGSERIAL PRIMARY KEY,')
      127 +                    parts.append(self.textify(key['name']))
      128 +                    vartype = key['type']
      129 +                    parts.append(self.TYPE_MAP.get(vartype, vartype.upper()))
      130 +                    if 'secondary' in key:
      131 +                        parts.append('UNIQUE NOT NULL')
      132 +                    if 'primary' in key:
      133 +                        parts.append('PRIMARY KEY')
      134 +                    if 'required' in key:
      135 +                        parts.append('NOT NULL')
      136 +                    if 'default' in key:
      137 +                        default = self.textify(key['default'])
      138 +                        parts.append(f"DEFAULT '{default}'")
      139 +                    if 'check' in key:
      140 +                        check = self.textify(key['check'])
      141 +                        parts.append(f'CHECK ({check})')
      142 +                    item = ' '.join(parts)
      143 +                if index < len(keys) - 1 or len(includes) > 0:
      144 +                    item = f'{item},'
      145 +                output.append(f'  {item}')
      146 +            if len(includes) > 0:
      147 +                joined = ', '.join(includes)
      148 +                output.append(f'  PRIMARY KEY ({joined})')
      149 +            output.append('};')
      150 +            # -----------------------------------------------------------------
      151
      139 -    def r_table(self, command):
      152 +        result = ECValue(domain='sql', type=str, content='\n'.join(output))
      153 +        self.putSymbolValue(target_record, result)
      154          return self.nextPC()
      155
      156      #############################################################################

● Let me run the test script to see what happens.

● Bash(cd /home/graham/dev/allspeak/allspeak-py && allspeak testsql.as 2>&1 | head -80)
  ⎿  AllSpeak version 260420.3
     Compiled TestSQL: 68 lines (174 tokens) in 5 ms
     Run TestSQL
     … +43 lines (ctrl+o to expand)

● Compiles and runs cleanly — all 7 tables generated.

  Summary of changes to plugins/as_sql.py:
  - Replaced the broken record['hasValue'] check with record.get('keyword') == 'variable'.
  - Replaced the broken entity['value'][entity['index']]['keys'] (old symbol-record shape) with a
  plugin-level self.tables dict keyed by target name; r_create populates it, r_get reads from it.
  - Fixed k_table to pass 'ECVariable' as the classname (was False, which silently left
  record['object'] as None).
  - Cleaned up the if/elif chain in key-flag parsing (primary/secondary/required/auto — first test
  was an if when it should have been elif).
  - Replaced the string type map. Previously vartype = str (the class) then vartype.upper() →
  TypeError at runtime. Now a clean TYPE_MAP dict: string→VARCHAR(255), text→TEXT, u64→BIGINT,
  datetime→TIMESTAMPTZ, uuid→UUID, with .upper() as fallback.
  - r_get now calls putSymbolValue(target_record, ECValue(...)) correctly (previous code built an
  ECValue but via the old value['tableName'] path that would have broken).

  One thing to note on output cosmetics (unchanged): the generated DDL still uses { / }; brackets
  rather than ( / );. That matches the original generator and isn't valid PostgreSQL — if that was a
  deliberate stylisation, leave it; if not, a one-line fix is swapping the two braces. Didn't touch
  it since you asked for compile-and-run, not SQL correctness.

  Not staged or committed — up to you whether you want it as its own commit.

✻ Cooked for 5m 50s
