Утилита для поиска ошибок в системных таблицах

Привет всем.

Пользователь провайдера, о котором я упоминал в «Адском домене», асилил обновление системной таблицы RDB$FIELDS и наконец-то обрел счастье и душевную умиротворенность. Вообще говоря, эти проблемы с доменами текстовых колонок Interbase — стандартные. Я бы даже сказал, что вообще все баги и тупости InterBase — это тщательно запрограммированный функционал. По другому ведь никак 🙂

Ключевую роль в победе над ошибками RDB$FIELDS сыграл VBS-скрипт, текст которого я приведу полностью. Хотя он и достаточно большой. Его последнюю версию можно найти в дистрибутиве — это файл prog_check_system_tables.vbs

Сам скрипт в базе ничего не меняет. Он работает в «READ ONLY» транзакции, анализирует текстовые колонки и формирует два файла: общий лог и шаблон скрипта с SQL запросами для исправления.

На текущий момент скрипт обнаруживает такие ошибки:
— Отсутствие кодовой страницы у текстовой колонки. Будут сгенерированы запросы, в которые потом нужно будет руками прописать идентификатор кодовой страницы.
— Рассогласование длины текстовой колонки в байтах и в символах.

—-
Параметры подключения к базе прописаны в константах, которые находятся в самом начале текста скрипта.

Так что, если у вас MSSQL ругается страшными ошибками вида

Message 7356, Level 16, State 1, Line 2
The OLE DB «LCPI.IBProvider.3» for linked server «FBLINKED» supplied inconsistent metadata for column. The column «COMPANY_NAME»(which had the compile-time ordinal 4) of the object «GENERIC_TABLE_X» appears to have «length» equal to 25 at compile time and 27 at run time.

попробуйте выполнит этот скрипт — скорее всего он найдет причину и упростит процесс исправления.

На всякий случай я напомню — сначала экспериментируйте с исправлениями на тестовой базе данных.

option explicit

'---------------------------------------------------------
const c_db_location="localhost:d:\database\ibp_test_fb25_d3_2.gdb"
const c_db_user    ="sysdba"
const c_db_pswd    ="masterkey"
const c_db_client  ="fbclient.dll"

const c_file_log         ="check_system_tables.log"
const c_file_correct_sql ="check_system_tables.sql"

'---------------------------------------------------------
const c_sql__unk_value ="<???>"

'---------------------------------------------------------
'BLR Types

const c_isc_blr_dtype__text         =14
const c_isc_blr_dtype__text2        =15
const c_isc_blr_dtype__short        =7
const c_isc_blr_dtype__long         =8
const c_isc_blr_dtype__quad         =9
const c_isc_blr_dtype__int64        =16
const c_isc_blr_dtype__float        =10
const c_isc_blr_dtype__double       =27
const c_isc_blr_dtype__d_float      =11
const c_isc_blr_dtype__timestamp    =35
const c_isc_blr_dtype__varying      =37
const c_isc_blr_dtype__varying2     =38
const c_isc_blr_dtype__blob         =261
const c_isc_blr_dtype__cstring      =40
const c_isc_blr_dtype__cstring2     =41
const c_isc_blr_dtype__blob_id      =45
const c_isc_blr_dtype__sql_date     =12
const c_isc_blr_dtype__sql_time     =13
 
const c_ib7_blr_dtype__bool         =17
 
const c_fb30_blr_dtype__bool        =23

'---------------------------------------------------------
dim g_fso
dim g_file_log
dim g_file_correct_sql

dim g_intend
dim g_ctx
dim g_cn
dim g_err_count

dim g_processed__domains

'------
set g_fso             =nothing
set g_file_log        =nothing
set g_file_correct_sql=nothing

g_intend=0

set g_ctx             =nothing
set g_cn              =nothing

g_err_count=0

'---------------------------------------------------------
set g_fso=createobject("Scripting.FileSystemObject")

set g_file_log=g_fso.CreateTextFile(c_file_log,true)

'---------------------------------------------------------
set g_ctx=new tag_context

set g_cn=createobject("ADODB.Connection")

g_cn.Provider="LCPI.IBProvider.3"
g_cn.Properties("location")         = c_db_location
g_cn.Properties("user id")          = c_db_user
g_cn.Properties("password")         = c_db_pswd
g_cn.Properties("dbclient_library") = c_db_client

call g_cn.Open()

call g_ctx.set_cn(g_cn)

'---------------------------------------------------------
print "DBMS Name:    "&cstr_sn(g_cn.Properties("DBMS Name").Value)
print "DBMS Version: "&cstr_sn(g_cn.Properties("DBMS Version").Value)
print "DBMS Dialect: "&cstr_sn(g_cn.Properties("IB Database Dialect").Value)

'----------------------------------------------------------
call g_ctx.cn.Execute("SET TRANSACTION READ ONLY")

call load_db_charsets(g_ctx)

call add(g_err_count,check_table_columns(g_ctx))

call g_ctx.cn.Execute("COMMIT")

set g_file_correct_sql=g_fso.CreateTextFile(c_file_correct_sql,true)

call g_ctx.write_correct_sql_to_file(g_file_correct_sql)

print ""
print "TOTAL ERRORS: "&cstr(g_err_count)

call wscript.quit(g_err_count)

'----------------------------------------------------------
class tag_charset_data
 public id
 public name
 public bytes_per_char
end class' tag_charset_data

'----------------------------------------------------------
private sub load_db_charsets(ctx)
 print ""
 print "load database charsets ..."

 dim rs
 set rs=ctx.cn.Execute _
  ("select rdb$character_set_name,"&vbCrLf&_
          "rdb$character_set_id,"&vbCrLf&_
          "rdb$bytes_per_character"&vbCrLf&_
          "from rdb$character_sets")

 dim db_csets_by_id
 set db_csets_by_id=createobject("Scripting.Dictionary")

 dim cset
 dim msg

 while(not rs.eof)
  set cset=new tag_charset_data

  cset.name           =get_nn(rs,0)
  cset.id             =get_nn(rs,1)
  cset.bytes_per_char =get_nn(rs,2)

  if(db_csets_by_id.Exists(cset.id))then
   msg="Detect multiple charsets with equal id ["&cset(cset.id)&"]"

   call err.raise(-1,,msg)
  end if

  call db_csets_by_id.Add(cset.id,cset)

  call rs.movenext()
 wend

 ctx.set_db_csets_by_id(db_csets_by_id)
end sub 'load_db_charsets

'----------------------------------------------------------
private function check_table_columns(ctx)
 check_table_columns=0

 print ""
 print "check table columns ..."

 dim rs
 set rs=ctx.cn.Execute _
  ("select rdb$relation_name,rdb$view_blr from rdb$relations")

 dim n_table
 n_table=0

 dim rel_name
 dim rel_type
 
 while(not rs.eof)
  n_table=n_table+1

  rel_name=get_nn(rs,0)

  if(IsNull(rs(1).value))then
   rel_type="TABLE"
  else
   rel_type="VIEW"
  end if

  print cstr(n_table)&". ["&rel_name&"] - {"&rel_type&"}"

  call add(check_table_columns, _
           check_table_columns__impl(ctx, _
                                     rel_name, _
                                     rel_type))

  call rs.movenext()
 wend
end function 'check_table_columns

'----------------------------------------------------------
private function check_table_columns__impl(ctx, _
                                           relation_name, _
                                           relation_type)
 check_table_columns__impl=0

 dim intend
 set intend=add_intend(5)

 dim cmd
 dim rs

 set cmd=createobject("ADODB.Command")
 cmd.ActiveConnection=ctx.cn

 cmd.CommandText= _
  "select rf.rdb$field_name as column_name,"&vbCrLf& _
          "f.*"&vbCrLf& _
  "from rdb$relation_fields rf"&vbCrLf& _
         "join rdb$fields f"&vbCrLf& _
        "on rf.rdb$field_source=f.rdb$field_name"&vbCrLf& _
  "where rf.rdb$relation_name=?"

 cmd(0)=relation_name

 set rs=cmd.Execute()

 dim col_name
 dim domain_data

 dim n_col
 n_col=0

 while(not rs.eof)
  n_col=n_col+1

  col_name=get_nn(rs,"column_name")

  print cstr(n_col)&". ["&col_name&"]"

  set domain_data=new tag_domain_data

  set domain_data.fields=rs.fields
  
  domain_data.data_location="{"&relation_type&"}["&relation_name&"].["&col_name&"]"

  set domain_data.sql_update_builder _
   =new tag_sql_builder__update_domain_of_relation_fields

  domain_data.sql_update_builder.relation_name =relation_name
  domain_data.sql_update_builder.field_name    =col_name

  call add(check_table_columns__impl, _
           check_domain_data(ctx,domain_data))

  call rs.movenext()
 wend

 if(n_col=0)then
  proces_error check_table_columns__impl, "No Columns!"
 end if
end function 'check_table_columns__impl

'----------------------------------------------------------
class tag_domain_data
 public fields
 public data_location

 public sql_update_builder

 private sub class_initialize()
   set fields=nothing

   data_location=""

   set sql_update_builder=nothing
 end sub 'class_initialize
end class 'tag_domain_data

'----------------------------------------------------------
private function check_domain_data(ctx,data)
 check_domain_data=0

 dim intend
 set intend=add_intend(5)

 dim domain_name
 domain_name=get_nn(data.fields,"RDB$FIELD_NAME")

 if(not ctx.mark_domain_as_processed(domain_name))then
  print "domain ["&domain_name&"] already processed"
  exit function
 end if

 print "domain ["&domain_name&"]"

 dim blr_type_id
 blr_type_id=get_nn(data.fields,"rdb$field_type")

 select case blr_type_id
  case c_isc_blr_dtype__text
   call add(check_domain_data,check_domain_data__text(ctx,data))

  case c_isc_blr_dtype__varying
   call add(check_domain_data,check_domain_data__varying(ctx,data))

  case else
   print "unknown data type"
 end select
end function 'check_domain_data

'----------------------------------------------------------
private function check_domain_data__text(ctx,data)
 check_domain_data__text=0

 print "CHAR"

 '-----------
 dim msg

 dim domain__name
 dim domain__is_system
 dim domain__cset_id
 dim domain__field_length
 dim domain__char_length
 dim domain__cset

 domain__name          =get_nn(data.fields,"RDB$FIELD_NAME")
 domain__is_system     =get_value_or_default(data.fields,"RDB$SYSTEM_FLAG",0)<>0

 domain__cset_id       =data.fields("RDB$CHARACTER_SET_ID").value
 domain__field_length  =data.fields("RDB$FIELD_LENGTH").value
 domain__char_length   =data.fields("RDB$CHARACTER_LENGTH").value

 set domain__cset=nothing

 '-----------
 dim valid_cset_id
 dim valid_field_length
 dim valid_char_length
 
 valid_cset_id      =true
 valid_field_length =true
 valid_char_length  =true

 '------------------------------- Check NOT NULL
 if(IsNull(domain__cset_id))then
  valid_cset_id=false
  valid_char_length=false

  process_error check_domain_data__text, "Column charset not defined"
 end if

 '-----------
 if(IsNull(domain__field_length))then
  valid_field_length=false

  process_error check_domain_data__text, "Length in bytes not defined"
 end if

 '-----------
 if(IsNull(domain__char_length))then
  valid_char_length=false

  if(not domain__is_system)then
   process_error check_domain_data__text, "Length in chars not defined"
  end if
 end if

 '------------------------------- Check charsetID
 if(valid_cset_id)then
  if(ctx.db_csets_by_id.Exists(domain__cset_id))then
   set domain__cset=ctx.db_csets_by_id.Item(domain__cset_id)
  else
   valid_cset_id=false

   process_error check_domain_data__text, "Unknown charsetID: "&cstr_sn(domain__cset_id)
  end if
 end if 'valid_cset_id

 '-------------------------------- Check char length
 if(valid_char_length and valid_cset_id and valid_field_length)then
  if((domain__char_length*domain__cset.bytes_per_char)<>domain__field_length)then
   valid_char_length=false

   msg="Wrong char count: "&cstr_sn(domain__char_length)&". "& _
       "BytesPerChar: "&cstr_sn(domain__cset.bytes_per_char)&". "& _
       "FieldLength: "&cstr_sn(domain__field_length)

   process_error check_domain_data__text, msg
  end if
 end if 'valid_char_length and valid_cset_id

 '-------------------------------- Create correction SQL
 dim comment
 dim old_value
 dim new_value
 dim sql
 dim sql_is_valid
 
 '-------------------------------- SQL FOR RDB$FIELD_LENGTH
 if(not valid_field_length)then
  old_value=domain__field_length
  
  sql_is_valid=valid_char_length and valid_cset_id

  if(sql_is_valid)then
   new_value=domain__char_length*domain__cset.bytes_per_char
  else
   new_value=c_sql__unk_value
  end if

  sql=data.sql_update_builder.gen(domain__name,         _
                                  "RDB$FIELD_LENGTH",   _
                                  old_value,            _
                                  new_value)

  '----
  comment="CHAR DOMAIN USED AT "&data.data_location

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if 'not valid_field_length

 '-------------------------------- SQL FOR RDB$CHARACTER_LENGTH
 if((not valid_char_length) and (not domain__is_system) and valid_cset_id)then
  old_value=domain__char_length

  if(not valid_field_length)then
   sql_is_valid=false
  else
   sql_is_valid=((domain__field_length mod domain__cset.bytes_per_char)=0)
  end if

  if(sql_is_valid)then
   new_value=domain__field_length / domain__cset.bytes_per_char
  else
   new_value=c_sql__unk_value
  end if
  
  sql=data.sql_update_builder.gen(domain__name,             _
                                  "RDB$CHARACTER_LENGTH",   _
                                  old_value,                _
                                  new_value)

  comment="CHAR DOMAIN USED AT "&data.data_location

  if(valid_cset_id)then
   comment=comment&vbCrLf
   comment=comment&"CSET_NAME      : ["&domain__cset.name&"]"&vbCrLf
   comment=comment&"CSET_ID        : ["&domain__cset.id&"]"&vbCrLf
   comment=comment&"BYTES_PER_CHAR : ["&domain__cset.bytes_per_char&"]"
  end if

  if(valid_field_length)then
   comment=comment&vbCrLf
   comment=comment&"FIELD_LENGTH   : ["&domain__field_length&"]"&vbCrLf
  end if

  comment=comment&vbCrLf
  comment=comment&"CURRENT CHAR_LENGTH: ["&cstr_sn(domain__char_length)&"]"

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if' not valid_char_length

 '-------------------------------- SQL FOR RDB$CHARACTER_SET_ID
 if(not valid_cset_id)then
  old_value=domain__cset_id
  new_value=c_sql__unk_value

  sql=data.sql_update_builder.gen(domain__name,           _
                                  "RDB$CHARACTER_SET_ID", _
                                  old_value,              _
                                  new_value)

  sql_is_valid=false

  comment="CHAR DOMAIN USED AT "&data.data_location

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if 'not valid_cset_id
end function 'check_domain_data__text

'----------------------------------------------------------
private function check_domain_data__varying(ctx,data)
 check_domain_data__varying=0

 print "VARCHAR"

 '-----------
 dim msg

 dim domain__name
 dim domain__is_system
 dim domain__cset_id
 dim domain__field_length
 dim domain__char_length
 dim domain__cset

 domain__name          =get_nn(data.fields,"RDB$FIELD_NAME")
 domain__is_system     =get_value_or_default(data.fields,"RDB$SYSTEM_FLAG",0)<>0

 domain__cset_id       =data.fields("RDB$CHARACTER_SET_ID").value
 domain__field_length  =data.fields("RDB$FIELD_LENGTH").value
 domain__char_length   =data.fields("RDB$CHARACTER_LENGTH").value

 set domain__cset=nothing

 '-----------
 dim valid_cset_id
 dim valid_field_length
 dim valid_char_length
 
 valid_cset_id      =true
 valid_field_length =true
 valid_char_length  =true

 '------------------------------- Check NOT NULL
 if(IsNull(domain__cset_id))then
  valid_cset_id=false
  valid_char_length=false

  process_error check_domain_data__varying, "Column charset not defined"
 end if

 '-----------
 if(IsNull(domain__field_length))then
  valid_field_length=false

  process_error check_domain_data__varying, "Length in bytes not defined"
 end if

 '-----------
 if(IsNull(domain__char_length))then
  valid_char_length=false

  if(not domain__is_system)then
   process_error check_domain_data__varying, "Length in chars not defined"
  end if
 end if

 '------------------------------- Check charsetID
 if(valid_cset_id)then
  if(ctx.db_csets_by_id.Exists(domain__cset_id))then
   set domain__cset=ctx.db_csets_by_id.Item(domain__cset_id)
  else
   valid_cset_id=false

   process_error check_domain_data__varying, "Unknown charsetID: "&cstr_sn(domain__cset_id)
  end if
 end if 'valid_cset_id

 '-------------------------------- Check char length
 if(valid_char_length and valid_cset_id and valid_field_length)then
  if((domain__char_length*domain__cset.bytes_per_char)<>domain__field_length)then
   valid_char_length=false

   msg="Wrong char count: "&cstr_sn(domain__char_length)&". "& _
       "BytesPerChar: "&cstr_sn(domain__cset.bytes_per_char)&". "& _
       "FieldLength: "&cstr_sn(domain__field_length)

   process_error check_domain_data__varying, msg
  end if
 end if 'valid_char_length and valid_cset_id

 '-------------------------------- Create correction SQL
 dim comment
 dim old_value
 dim new_value
 dim sql
 dim sql_is_valid
 
 '-------------------------------- SQL FOR RDB$FIELD_LENGTH
 if(not valid_field_length)then
  old_value=domain__field_length
  
  sql_is_valid=valid_char_length and valid_cset_id

  if(sql_is_valid)then
   new_value=domain__char_length*domain__cset.bytes_per_char
  else
   new_value=c_sql__unk_value
  end if

  sql=data.sql_update_builder.gen(domain__name,         _
                                  "RDB$FIELD_LENGTH",   _
                                  old_value,            _
                                  new_value)

  '----
  comment="VARCHAR DOMAIN USED AT "&data.data_location

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if 'not valid_field_length

 '-------------------------------- SQL FOR RDB$CHARACTER_LENGTH
 if((not valid_char_length) and (not domain__is_system) and valid_cset_id)then
  old_value=domain__char_length

  if(not valid_field_length)then
   sql_is_valid=false
  else
   sql_is_valid=((domain__field_length mod domain__cset.bytes_per_char)=0)
  end if

  if(sql_is_valid)then
   new_value=domain__field_length / domain__cset.bytes_per_char
  else
   new_value=c_sql__unk_value
  end if
  
  sql=data.sql_update_builder.gen(domain__name,             _
                                  "RDB$CHARACTER_LENGTH",   _
                                  old_value,                _
                                  new_value)

  comment="VARCHAR DOMAIN USED AT "&data.data_location

  if(valid_cset_id)then
   comment=comment&vbCrLf
   comment=comment&"CSET_NAME      : ["&domain__cset.name&"]"&vbCrLf
   comment=comment&"CSET_ID        : ["&domain__cset.id&"]"&vbCrLf
   comment=comment&"BYTES_PER_CHAR : ["&domain__cset.bytes_per_char&"]"
  end if

  if(valid_field_length)then
   comment=comment&vbCrLf
   comment=comment&"FIELD_LENGTH   : ["&domain__field_length&"]"&vbCrLf
  end if

  comment=comment&vbCrLf
  comment=comment&"CURRENT CHAR_LENGTH: ["&cstr_sn(domain__char_length)&"]"

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if' not valid_char_length

 '-------------------------------- SQL FOR RDB$CHARACTER_SET_ID
 if(not valid_cset_id)then
  old_value=domain__cset_id
  new_value=c_sql__unk_value

  sql=data.sql_update_builder.gen(domain__name,           _
                                  "RDB$CHARACTER_SET_ID", _
                                  old_value,              _
                                  new_value)

  sql_is_valid=false

  comment="VARCHAR DOMAIN USED AT "&data.data_location

  '----
  call ctx.reg_correct_sql(sql_is_valid,sql,comment)
 end if 'not valid_cset_id
end function 'check_domain_data__varying

'----------------------------------------------------------
private function get_nn(rs,field_index)
 get_nn=rs(field_index).value

 if(IsNull(get_nn))then
  dim msg
  msg="Field ["&rs(field_index).name&"] has NULL value"

  call err.raise(-1,,msg)
 end if
end function 'get_nn

'----------------------------------------------------------
private function get_value_or_default(rs,field_index,default_value)
 get_value_or_default=rs(field_index).value

 if(IsNull(get_value_or_default))then
  get_value_or_default=default_value
 end if
end function 'get_nn

'----------------------------------------------------------
class tag_intend
 private m_prev_intend

 private sub class_initialize()
  m_prev_intend=g_intend
 end sub 'class_inialize

 private sub class_terminate()
  g_intend=m_prev_intend
 end sub 'class_terminate
end class 'tag_intend

'----------------------------------------------------------
private function add_intend(delta)
 set add_intend=new tag_intend

 call add(g_intend,delta)
end function 'add_intend

'----------------------------------------------------------
private sub process_error(byref err_count,msg)
 call add(err_count,1)

 print_error msg
end sub 'process_error

'----------------------------------------------------------
private sub print_error(msg)
 print "ERROR: "&msg
end sub 'print_error

'----------------------------------------------------------
private sub print(msg)
 out_text string(g_intend," ")&msg
end sub 'print

'----------------------------------------------------------
private sub out_text(msg)
 wscript.echo msg

 if(not(g_file_log is nothing))then
  call g_file_log.WriteLine(msg)
 end if
end sub 'out_text

'----------------------------------------------------------
private function cstr_sn(x)
 if(IsNull(x))then
  cstr_sn="#NULL"
 elseif(IsEmpty(x))then
  cstr_sn="#EMPTY"
 else
  cstr_sn=cstr(x)
 end if
end function 'cstr_sn

'----------------------------------------------------------
private function inc(byref x)
 inc=add(x,1)
end function 'inc
'----------------------------------------------------------

private function add(byref x,byval delta)
 x=x+delta

 add=x
end function 'add

'----------------------------------------------------------
private function sql__pack_name(name)
 dim s

 s="'"

 dim i
 for i=1 to len(name)
  if(mid(name,i,1)="'")then
   s=s&"'"
  end if

  s=s&mid(name,i,1)
 next 'i

 s=s&"'"

 sql__pack_name=s
end function 'sql__pack_name

'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'class tag_correct_sql

class tag_correct_sql
 public sql_is_valid
 public sql
 public comment
end class 'tag_correct_sql

'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'class tag_context

class tag_context
 private m_cn
 private m_db_csets_by_id

 private m_processed__domains

 private m_correct_sql

 '----------------------------------------------
 private sub class_initialize()
  set m_cn=nothing

  set m_db_csets_by_id=nothing

  set m_processed__domains=nothing

  set m_correct_sql=nothing
 end sub 'class_initialize

 '----------------------------------------------
 public sub set_cn(cn)
  set m_cn=cn
 end sub 'set_cn

 '----------------------------------------------
 public sub set_db_csets_by_id(db_csets_by_id)
  set m_db_csets_by_id=db_csets_by_id
 end sub 'set_db_csets_by_id

 '----------------------------------------------
 public property get cn()
  set cn=m_cn
 end property 'get cn

 '----------------------------------------------
 public property get db_csets_by_id()
  set db_csets_by_id=m_db_csets_by_id
 end property 'get csets_by_id

 '----------------------------------------------
 public function mark_domain_as_processed(domain_name)
  mark_domain_as_processed=false

  if(m_processed__domains is nothing)then
   set m_processed__domains=createobject("Scripting.Dictionary")
  elseif(m_processed__domains.Exists(domain_name))then
   exit function
  end if
  
  call m_processed__domains.add(domain_name,null)

  mark_domain_as_processed=true
 end function 'mark_domain_as_processed

 '----------------------------------------------
 public sub reg_correct_sql(sql_is_valid,sql,comment)
  if(m_correct_sql is nothing)then
   set m_correct_sql=createobject("LCPI.IBP.Samples.Vector.1")
  end if

  dim x
  set x=new tag_correct_sql

  x.sql_is_valid =sql_is_valid
  x.sql          =sql
  x.comment      =comment

  call m_correct_sql.PushBack(x)
 end sub 'reg_correct_sql

 '----------------------------------------------
 public property get has_correct_sql
  has_correct_sql=false

  if(m_correct_sql is nothing)then
   exit property
  end if

  if(m_correct_sql.IsEmpty())then
   exit property
  end if

  has_correct_sql=true
 end property 'get has_correct_sql
 
 '----------------------------------------------
 public sub write_correct_sql_to_file(file)
  if(not me.has_correct_sql)then
   exit sub
  end if
  
  dim n
  n=0

  dim data
  dim i

  for i=0 to m_correct_sql.Count-1
   n=n+1

   set data=m_correct_sql(i)

   call file.WriteLine _
    ("/*"&n&" --------------------------------- */")

   call file.WriteLine _
    ("/*"&vbCrLf&data.comment&vbCrLf&"*/")

   call file.WriteLine _
    ("")

   call file.WriteLine _
    (data.sql)
  next 'i
 
  call file.WriteLine _
    ("")

  call file.WriteLine _
    ("/* --------------------------------- */")

  call file.WriteLine _
    ("COMMIT;")

  call file.WriteLine _
    ("/**/")
 end sub 'write_correct_sql_to_file
end class 'tag_context

'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class tag_sql_builder__update_domain_of_relation_fields
 public relation_name
 public field_name

 public function gen(domain_name,           _
                     domain_prop_name,      _
                     domain_prop_old_value, _
                     domain_prop_new_value)
  dim s

  s="UPDATE RDB$FIELDS"&vbCrLf& _
    "SET "&domain_prop_name&"="
    
  if(IsNull(domain_prop_new_value))then
   s=s&"NULL"
  else
   s=s&cstr(domain_prop_new_value)
  end if

  s=s&vbCrLf& _
    "WHERE RDB$FIELD_NAME="&vbCrLf& _
    "       (SELECT RDB$FIELD_SOURCE"&vbCrLf& _
    "        FROM RDB$RELATION_FIELDS"&vbCrLf& _
    "        WHERE RDB$RELATION_NAME="&sql__pack_name(relation_name)&" AND"&vbCrLf& _
    "              RDB$FIELD_NAME="&sql__pack_name(field_name)&") AND"&vbCrLf& _
    "     "&domain_prop_name

  if(IsNull(domain_prop_old_value))then
   s=s&" IS NULL"
  else
   s=s&"="&cstr(domain_prop_old_value)
  end if

  s=s&";"

  gen=s
 end function 'gen
end class 'tag_sql_builder__update_domain_of_relation_fields

'++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

На выходе будет сформирован, к примеру, вот такой файл с исправлениями:

/*1 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[PROFILE].[CREATEBY]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [20]

CURRENT CHAR_LENGTH: [10]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=20
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PROFILE' AND
              RDB$FIELD_NAME='CREATEBY') AND
     RDB$CHARACTER_LENGTH=10;
/*2 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[PAYMENT].[CALCFIXEDCCNO]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [35]

CURRENT CHAR_LENGTH: [0]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=35
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PAYMENT' AND
              RDB$FIELD_NAME='CALCFIXEDCCNO') AND
     RDB$CHARACTER_LENGTH=0;
/*3 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[INVOICE].[REMARKS]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [4000]

CURRENT CHAR_LENGTH: [512]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=4000
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='INVOICE' AND
              RDB$FIELD_NAME='REMARKS') AND
     RDB$CHARACTER_LENGTH=512;
/*4 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[INVOICE].[CALCINVOICENUMBER]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [11]

CURRENT CHAR_LENGTH: [0]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=11
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='INVOICE' AND
              RDB$FIELD_NAME='CALCINVOICENUMBER') AND
     RDB$CHARACTER_LENGTH=0;
/*5 --------------------------------- */
/*
CHAR DOMAIN USED AT {TABLE}[BOOKING].[CALCTRIMVENDORREMARKS]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [0]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKING' AND
              RDB$FIELD_NAME='CALCTRIMVENDORREMARKS') AND
     RDB$CHARACTER_LENGTH=0;
/*6 --------------------------------- */
/*
CHAR DOMAIN USED AT {TABLE}[BOOKING].[CALCTRIMCLIENTREMARKS]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [0]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKING' AND
              RDB$FIELD_NAME='CALCTRIMCLIENTREMARKS') AND
     RDB$CHARACTER_LENGTH=0;
/*7 --------------------------------- */
/*
CHAR DOMAIN USED AT {TABLE}[BOOKING].[CALCTICKETCONFIRMNO]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [0]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKING' AND
              RDB$FIELD_NAME='CALCTICKETCONFIRMNO') AND
     RDB$CHARACTER_LENGTH=0;
/*8 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {VIEW}[PROFILEVIEW].[FIRSTANDLAST]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [81]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=81
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PROFILEVIEW' AND
              RDB$FIELD_NAME='FIRSTANDLAST') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*9 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {VIEW}[PROFILEVIEW].[LASTANDFIRST]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [81]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=81
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PROFILEVIEW' AND
              RDB$FIELD_NAME='LASTANDFIRST') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*10 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {VIEW}[PROFILEVIEW].[FIRSTANDLASTUPPER]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [81]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=81
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PROFILEVIEW' AND
              RDB$FIELD_NAME='FIRSTANDLASTUPPER') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*11 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {VIEW}[PROFILEVIEW].[LASTANDFIRSTUPPER]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [81]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=81
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PROFILEVIEW' AND
              RDB$FIELD_NAME='LASTANDFIRSTUPPER') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*12 --------------------------------- */
/*
CHAR DOMAIN USED AT {VIEW}[BOOKINGVIEW].[CLIENTREMARKS]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKINGVIEW' AND
              RDB$FIELD_NAME='CLIENTREMARKS') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*13 --------------------------------- */
/*
CHAR DOMAIN USED AT {VIEW}[BOOKINGVIEW].[VENDORREMARKS]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKINGVIEW' AND
              RDB$FIELD_NAME='VENDORREMARKS') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*14 --------------------------------- */
/*
CHAR DOMAIN USED AT {VIEW}[BOOKINGVIEW].[TICKETCONFIRMNO]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [253]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=253
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKINGVIEW' AND
              RDB$FIELD_NAME='TICKETCONFIRMNO') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*15 --------------------------------- */
/*
CHAR DOMAIN USED AT {VIEW}[BOOKINGPSGRVIEW].[PASSENGERDEPTUPPER]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [254]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=254
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKINGPSGRVIEW' AND
              RDB$FIELD_NAME='PASSENGERDEPTUPPER') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*16 --------------------------------- */
/*
CHAR DOMAIN USED AT {VIEW}[BOOKINGPSGRVIEW].[PASSENGERDEPT]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [254]

CURRENT CHAR_LENGTH: [#NULL]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=254
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='BOOKINGPSGRVIEW' AND
              RDB$FIELD_NAME='PASSENGERDEPT') AND
     RDB$CHARACTER_LENGTH IS NULL;
/*17 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[PNRBRANCHSETTINGS].[PSEUDOCITY]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [8]

CURRENT CHAR_LENGTH: [3]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=8
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='PNRBRANCHSETTINGS' AND
              RDB$FIELD_NAME='PSEUDOCITY') AND
     RDB$CHARACTER_LENGTH=3;
/*18 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[ACTIVITY].[USERNAME]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [20]

CURRENT CHAR_LENGTH: [15]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=20
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='ACTIVITY' AND
              RDB$FIELD_NAME='USERNAME') AND
     RDB$CHARACTER_LENGTH=15;
/*19 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[RESCARDRESERVATION].[SOURCEOFBOOKING]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [25]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=25
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='RESCARDRESERVATION' AND
              RDB$FIELD_NAME='SOURCEOFBOOKING') AND
     RDB$CHARACTER_LENGTH=20;
/*20 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[RESCARD].[GROUPNAME]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [128]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=128
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='RESCARD' AND
              RDB$FIELD_NAME='GROUPNAME') AND
     RDB$CHARACTER_LENGTH=20;
/*21 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[RESCARDPROVIDER].[DINING]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [30]

CURRENT CHAR_LENGTH: [15]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=30
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='RESCARDPROVIDER' AND
              RDB$FIELD_NAME='DINING') AND
     RDB$CHARACTER_LENGTH=15;
/*22 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[RESCARDPROVIDER].[AIRCRAFTLOCATOR]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [12]

CURRENT CHAR_LENGTH: [10]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=12
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='RESCARDPROVIDER' AND
              RDB$FIELD_NAME='AIRCRAFTLOCATOR') AND
     RDB$CHARACTER_LENGTH=10;
/*23 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[LIVECONNECTPROVIDER].[PROVIDERNAME]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [60]

CURRENT CHAR_LENGTH: [30]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=60
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='LIVECONNECTPROVIDER' AND
              RDB$FIELD_NAME='PROVIDERNAME') AND
     RDB$CHARACTER_LENGTH=30;
/*24 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[MARKETINGCODE].[CODEVALUE]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [80]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=80
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='MARKETINGCODE' AND
              RDB$FIELD_NAME='CODEVALUE') AND
     RDB$CHARACTER_LENGTH=20;
/*25 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[VENDORLIVECONNECT].[VENDORCODE]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [128]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=128
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='VENDORLIVECONNECT' AND
              RDB$FIELD_NAME='VENDORCODE') AND
     RDB$CHARACTER_LENGTH=20;
/*26 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[USERLIVECONNECT].[LCPASSWORD]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [80]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=80
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='USERLIVECONNECT' AND
              RDB$FIELD_NAME='LCPASSWORD') AND
     RDB$CHARACTER_LENGTH=20;
/*27 --------------------------------- */
/*
VARCHAR DOMAIN USED AT {TABLE}[USERLIVECONNECT].[USERNAME]
CSET_NAME      : [NONE]
CSET_ID        : [0]
BYTES_PER_CHAR : [1]
FIELD_LENGTH   : [80]

CURRENT CHAR_LENGTH: [20]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_LENGTH=80
WHERE RDB$FIELD_NAME=
       (SELECT RDB$FIELD_SOURCE
        FROM RDB$RELATION_FIELDS
        WHERE RDB$RELATION_NAME='USERLIVECONNECT' AND
              RDB$FIELD_NAME='USERNAME') AND
     RDB$CHARACTER_LENGTH=20;

/* --------------------------------- */
COMMIT;
/**/

One Comment

Kovalenko  on 27 сентября, 2011

Обновление скрипта.

1. Исправил багу, которая обнаружилась в процессе полевого испытания.
2. Добавил константу «c_db_client», которая упрощает указание клиентской библиотеки.

Пример запроса, который будет сформирован, если обнаружится текстовая колонка без кодовой страницы (это болезнь VIEW баз данных Interbase).

/*
VARCHAR DOMAIN USED AT {VIEW}[PNM_BYDESCR_VIEW].[DESCRIPTION]
*/

UPDATE RDB$FIELDS
SET RDB$CHARACTER_SET_ID=
WHERE RDB$FIELD_NAME=
(SELECT RDB$FIELD_SOURCE
FROM RDB$RELATION_FIELDS
WHERE RDB$RELATION_NAME=’PNM_BYDESCR_VIEW’ AND
RDB$FIELD_NAME=’DESCRIPTION’) AND
RDB$CHARACTER_SET_ID IS NULL;

Leave a Comment