Skip to content

Installation/Patch failing on PostgreSQL due to UndefinedColumn "HR Settings" (SQL syntax error) #4408

@speters12

Description

@speters12

Information about bug

Description:

Environment:

  • Backend Container: FROM frappe/erpnext:v16.13.3
  • Database: PostgreSQL 16
  • Python version: 3.14 (experimental)

Context:
While installing the hrms app on a site using a PostgreSQL database, the installation fails during the after_install patches. Specifically, the patch move_payroll_setting_separately_from_hr_settings contains a SQL query that uses double quotes for string literals, which PostgreSQL interprets as column identifiers.

Steps to reproduce:

  1. Setup a Frappe/ERPNext instance with PostgreSQL.
  2. Run bench get-app hrms.
  3. Run bench --site [your-site] install-app hrms.

Observed Result:
The installation fails with the following traceback:

psycopg2.errors.UndefinedColumn: column "HR Settings" does not exist
LINE 4:             doctype = "HR Settings"

Root Cause:
In hrms/patches/post_install/move_payroll_setting_separately_from_hr_settings.py, the SQL query is:

data = frappe.db.sql("""
    SELECT *
    FROM `tabSingles`
    WHERE
        doctype = "HR Settings"
    AND
        field in (...)
""", as_dict=1)

In PostgreSQL, "HR Settings" is treated as a column name. For string literals, single quotes 'HR Settings' must be used to ensure cross-database compatibility (MySQL/MariaDB and PostgreSQL).

Expected Result:
The app should install successfully without SQL syntax errors.

Module

HR

Version

erpnext 16.13.3 ()
frappe 16.15.0 ()
hrms 16.5.0 version-16 (10379d4)

Installation method

docker

Relevant log output / Stack trace / Full Error Message.

frappe@cb8d870af36d:~/frappe-bench$ bench --site shirtful-erpnext.amicaldo.net install-app hrms
App erpnext already installed

Installing hrms...
Updating DocTypes for hrms          : [========================================] 100%
Setting up Frappe HR...

Patching Existing Data...
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 177, in <module>
    main()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 77, in main
    FrappeClickWrapper(click.Group, handle_exception)(commands=commands)(prog_name="bench")
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 1485, in __call__
    return self.main(*args, **kwargs)
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 1406, in main
    rv = self.invoke(ctx)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 48, in invoke
    return super().invoke(ctx)
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 1873, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 48, in invoke
    return super().invoke(ctx)
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 1873, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 1269, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/core.py", line 824, in invoke
    return callback(*args, **kwargs)
  File "/home/frappe/frappe-bench/env/lib/python3.14/site-packages/click/decorators.py", line 34, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/commands/__init__.py", line 28, in _func
    ret = f(ctx.obj, *args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/commands/site.py", line 522, in install_app
    _install_app(app, verbose=context.verbose, force=force)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/installer.py", line 333, in install_app
    frappe.get_attr(after_install)()
  File "/home/frappe/frappe-bench/apps/hrms/hrms/install.py", line 9, in after_install
    setup()
  File "/home/frappe/frappe-bench/apps/hrms/hrms/setup.py", line 22, in after_install
    run_post_install_patches()
  File "/home/frappe/frappe-bench/apps/hrms/hrms/setup.py", line 578, in run_post_install_patches
    frappe.get_attr(f"hrms.patches.post_install.{patch_name}.execute")()
  File "/home/frappe/frappe-bench/apps/hrms/hrms/patches/post_install/move_payroll_setting_separately_from_hr_settings.py", line 9, in execute
    data = frappe.db.sql(
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/postgres/database.py", line 234, in sql
    return super().sql(modify_query(query), modify_values(values), *args, **kwargs)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/database/database.py", line 294, in sql
    traceback.print_stack()
Error in query:
column "HR Settings" does not exist
LINE 4:             doctype = "HR Settings"
                              ^

Installation for Frappe HR app failed due to an error. Please try re-installing the app or report the issue on https://github.com/frappe/hrms/issues/new if not resolved.
An error occurred while installing hrms: column "HR Settings" does not exist
LINE 4:             doctype = "HR Settings"
                              ^

Traceback with variables (most recent call last):
  File "apps/frappe/frappe/commands/site.py", line 522, in install_app
    _install_app(app, verbose=context.verbose, force=force)
      context = CliCtxObj(force=False, profile=False, sites=['shirtful-erpnext.amicaldo.net'], verbose=False)
      apps = ('hrms')
      force = False
      _install_app = <function install_app at 0x77bf8c4abed0>
      filelock = <function filelock at 0x77bf8d4b19b0>
      exit_code = 0
      site = 'shirtful-erpnext.amicaldo.net'
      app = 'hrms'
      err = column "HR Settings" does not exist
      LINE 4:             doctype = "HR Settings"
                                    ^
      
  File "apps/frappe/frappe/installer.py", line 333, in install_app
    frappe.get_attr(after_install)()
      name = 'hrms'
      verbose = False
      set_as_patched = True
      force = False
      sync_jobs = <function sync_jobs at 0x77bf8a96c670>
      sync_for = <function sync_for at 0x77bf8a96d2d0>
      sync_customizations = <function sync_customizations at 0x77bf8d50f480>
      sync_fixtures = <function sync_fixtures at 0x77bf8a96e350>
      installed_apps = ['frappe', 'erpnext', 'amicaldo_custom']
      app = 'frappe/erpnext'
      required_app = 'erpnext'
      other_class_overrides = []
      after_install = 'hrms.install.after_install'
      app_hooks = {'accounting_dimension_doctypes': ['Expense Claim', 'Expense Claim Detail', 'Expense Taxes and Charges', 'Payroll Entry', 'Leave Encashment'], 'add_to_apps_screen': [{'name': 'hrms', 'logo': '/assets/hrms/images/frappe-hr-logo.svg', 'title': 'Frappe HR', 'route': '/desk/people', 'has_permission': 'hrms.hr.utils.check_app_permission'}], 'advance_payment_payable_doctypes': ['Leave Encashment', 'Gratuity', 'Employee Advance'], 'after_app_install': ['hrms.setup.after_app_install'], 'after_install': ['hrms.install.after_install'], 'after_migrate': ['hrms.setup.update_select_perm_after_install'], 'app_description': ['Modern HR and Payroll Software'], 'app_email': ['contact@frappe.io'], 'app_home':...rd_complete': ['hrms.subscription_utils.update_erpnext_access'], 'source_link': ['http://github.com/frappe/hrms'], 'website_generators': ['Job Opening'], 'website_route_rules': [{'from_route': '/hrms/<path:app_path>', 'to_route': 'hrms'}, {'from_route': '/hr/<path:app_path>', 'to_route': 'roster'}]}
  File "apps/hrms/hrms/install.py", line 21, in after_install
    raise e
      BUG_REPORT_URL = 'https://github.com/frappe/hrms/issues/new'
  File "apps/hrms/hrms/install.py", line 9, in after_install
    setup()
      BUG_REPORT_URL = 'https://github.com/frappe/hrms/issues/new'
  File "apps/hrms/hrms/setup.py", line 22, in after_install
    run_post_install_patches()
  File "apps/hrms/hrms/setup.py", line 578, in run_post_install_patches
    frappe.get_attr(f"hrms.patches.post_install.{patch_name}.execute")()
      POST_INSTALL_PATCHES = ('erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab', 'erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll', 'erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings', 'erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment', 'erpnext.patches.v13_0.updates_for_multi_currency_payroll', 'erpnext.patches.v13_0.update_reason_for_resignation_in_employee', 'erpnext.patches.v13_0.set_company_in_leave_ledger_entry', 'erpnext.patches.v13_0.rename_stop_to_send_birthday_reminders', 'erpnext.patches.v13_0.set_training_event_attendance', 'erpnext.patches.v14_0.set_payroll_cost_centers', 'erpnext.patches.v13_0.update_empl...nce_status', 'erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances', 'erpnext.patches.v14_0.delete_employee_transfer_property_doctype', 'erpnext.patches.v13_0.set_payroll_entry_status', 'create_country_fixtures', 'update_allocate_on_in_leave_type', 'update_performance_module_changes')
      patch = 'erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings'
      patch_name = 'move_payroll_setting_separately_from_hr_settings'
  File "apps/hrms/hrms/patches/post_install/move_payroll_setting_separately_from_hr_settings.py", line 9, in execute
    data = frappe.db.sql(
  File "apps/frappe/frappe/database/postgres/database.py", line 234, in sql
    return super().sql(modify_query(query), modify_values(values), *args, **kwargs)
      self = frappe.database.postgres.database.PostgresDatabase(after_commit=frappe.utils.CallbackManager(-), after_rollback=frappe.utils.CallbackManager(-), auto_commit_on_many_writes=0, before_commit=frappe.utils.CallbackManager(-), before_rollback=frappe.utils.CallbackManager(-), cur_db_name='_0fb7d5750058b515', db_type='postgres', host='shirtful-erpnext-db', logger=<Logger database-shirtful-erpnext.amicaldo.net (WARNING)>, password='8hZNy9S8paUs07DR', port=5432, socket=None, transaction_writes=885, type_map={'Currency': ('decimal', '21,9'), 'Int': ('int', None), 'Long Int': ('bigint', None), 'Float': ('decimal', '21,9'), 'Percent': ('decimal', '21,9'), 'Check': ('smallint', None), 'Small Text': ('....frappedict._dict{'name': 'Leave Approver'}]}, 'Expense Approver': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Expense Approver'}]}}, 'DocType': collections.defaultdict{'Employee': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Employee'}]}}})
      query = 'SELECT *
              FROM `tabSingles`
              WHERE
                  doctype = "HR Settings"
              AND
                  field in (
                      "encrypt_salary_slips_in_emails",
                      "email_salary_slip_to_employee",
                      "daily_wages_fraction_for_half_day",
                      "disable_rounded_total",
                      "include_holidays_in_total_working_days",
                      "max_working_hours_against_timesheet",
                      "payroll_based_on",
                      "password_policy"
                  )
                  '
      values = <exception while printing> Traceback (most recent call last):
          ...traceback_with_variables internal stacks...
          File "apps/frappe/frappe/utils/__init__.py", line 380, in dict_printer
            if key in v:
               ^^^^^^^^
        TypeError: argument of type 'object' is not a container or iterable
        
      args = ()
      kwargs = {'as_dict': 1}
      __class__ = frappe.database.postgres.database.PostgresDatabase
  File "apps/frappe/frappe/database/database.py", line 272, in sql
    self.execute_query(query, values)
      self = frappe.database.postgres.database.PostgresDatabase(after_commit=frappe.utils.CallbackManager(-), after_rollback=frappe.utils.CallbackManager(-), auto_commit_on_many_writes=0, before_commit=frappe.utils.CallbackManager(-), before_rollback=frappe.utils.CallbackManager(-), cur_db_name='_0fb7d5750058b515', db_type='postgres', host='shirtful-erpnext-db', logger=<Logger database-shirtful-erpnext.amicaldo.net (WARNING)>, password='8hZNy9S8paUs07DR', port=5432, socket=None, transaction_writes=885, type_map={'Currency': ('decimal', '21,9'), 'Int': ('int', None), 'Long Int': ('bigint', None), 'Float': ('decimal', '21,9'), 'Percent': ('decimal', '21,9'), 'Check': ('smallint', None), 'Small Text': ('....frappedict._dict{'name': 'Leave Approver'}]}, 'Expense Approver': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Expense Approver'}]}}, 'DocType': collections.defaultdict{'Employee': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Employee'}]}}})
      query = 'SELECT *
              FROM "tabSingles"
              WHERE
                  doctype = "HR Settings"
              AND
                  field in (
                      "encrypt_salary_slips_in_emails",
                      "email_salary_slip_to_employee",
                      "daily_wages_fraction_for_half_day",
                      "disable_rounded_total",
                      "include_holidays_in_total_working_days",
                      "max_working_hours_against_timesheet",
                      "payroll_based_on",
                      "password_policy"
                  )'
      values = None
      as_dict = 1
      as_list = 0
      debug = False
      ignore_ddl = 0
      auto_commit = 0
      update = None
      explain = False
      run = True
      pluck = False
      as_iterator = False
      query_type = 'select'
      trace_id = None
  File "apps/frappe/frappe/database/database.py", line 372, in execute_query
    return self._cursor.execute(query, values)
      self = frappe.database.postgres.database.PostgresDatabase(after_commit=frappe.utils.CallbackManager(-), after_rollback=frappe.utils.CallbackManager(-), auto_commit_on_many_writes=0, before_commit=frappe.utils.CallbackManager(-), before_rollback=frappe.utils.CallbackManager(-), cur_db_name='_0fb7d5750058b515', db_type='postgres', host='shirtful-erpnext-db', logger=<Logger database-shirtful-erpnext.amicaldo.net (WARNING)>, password='8hZNy9S8paUs07DR', port=5432, socket=None, transaction_writes=885, type_map={'Currency': ('decimal', '21,9'), 'Int': ('int', None), 'Long Int': ('bigint', None), 'Float': ('decimal', '21,9'), 'Percent': ('decimal', '21,9'), 'Check': ('smallint', None), 'Small Text': ('....frappedict._dict{'name': 'Leave Approver'}]}, 'Expense Approver': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Expense Approver'}]}}, 'DocType': collections.defaultdict{'Employee': collections.defaultdict{('name'): [frappe.types.frappedict._dict{'name': 'Employee'}]}}})
      query = 'SELECT *
              FROM "tabSingles"
              WHERE
                  doctype = "HR Settings"
              AND
                  field in (
                      "encrypt_salary_slips_in_emails",
                      "email_salary_slip_to_employee",
                      "daily_wages_fraction_for_half_day",
                      "disable_rounded_total",
                      "include_holidays_in_total_working_days",
                      "max_working_hours_against_timesheet",
                      "payroll_based_on",
                      "password_policy"
                  )'
      values = None
psycopg2.errors.UndefinedColumn: column "HR Settings" does not exist
LINE 4:             doctype = "HR Settings"
                              ^

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions