Страница 1 из 1

MS SQL, проблема с кодировкой при чтении через ODBC

Добавлено: 21 окт 2016, 11:37
ronaldonn
Приветствую, уважаемые форумчане!
Было проделано следующее:
В базе MSSQL (пробовались версии 2008/2012/2014) создана таблица:

Код: Выделить всё

use IPtel;
CREATE TABLE Numbers (
      [id] int IDENTITY(1,1) NOT NULL,
      [name] nvarchar(MAX)   NULL,
      [ipaddr] nvarchar(MAX)   NULL,
      [port] int  NULL,
      [regseconds] int  NULL,
      [user] nvarchar(MAX)    NULL,
      [fullcontact] nvarchar(MAX)   NULL,
      [regserver] nvarchar(MAX)  NULL,
      [useragent] nvarchar(MAX)  NULL,
      [lastms] int  NULL,
      [host] nvarchar(MAX)  NOT NULL DEFAULT 'dynamic',
      [type] nvarchar(MAX)  NOT NULL DEFAULT 'friend',
      [context] nvarchar(MAX)  NOT NULL DEFAULT  'BDD-INCOMING',
      [permit] nvarchar(MAX) NOT NULL DEFAULT  '0.0.0.0/0',
      [deny] nvarchar(MAX)  NULL,
      [secret] nvarchar(MAX)  NULL,
      [md5secret] nvarchar(MAX)  NULL,
      [remotesecret] nvarchar(MAX)  NULL,
      [transport] nvarchar(MAX)  NOT NULL DEFAULT  'udp',
      [dtmfmode] nvarchar(MAX)  NOT NULL DEFAULT 'rfc2833',
      [directmedia] nvarchar(MAX)  NOT NULL DEFAULT 'no',
      [nat] nvarchar(MAX)  NOT NULL DEFAULT 'force_rport,comedia',
      [callgroup] nvarchar(MAX)  NULL,
      [pickupgroup] nvarchar(MAX)  NULL,
      [language] nvarchar(MAX)  NULL,
      [allow] nvarchar(MAX)  NOT NULL DEFAULT '!all,alaw',
      [disallow] nvarchar(MAX)  NULL,
      [insecure] nvarchar(MAX)  NULL,
      [trustrpid] nvarchar(MAX)  NULL,
      [progressinband] nvarchar(MAX)  NULL,
      [promiscredir] nvarchar(MAX)  NULL,
      [useclientcode] nvarchar(MAX)  NULL,
      [accountcode] nvarchar(MAX)  NULL,
      [setvar] nvarchar(MAX)  NULL,
      [callerid] nvarchar(MAX)  NULL,
      [amaflags] nvarchar(MAX)  NULL,
      [callcounter] nvarchar(MAX)  NULL,
      [busylevel] int  NULL,
      [allowoverlap] nvarchar(MAX)  NULL,
      [allowsubscribe] nvarchar(MAX)  NULL,
      [videosupport] nvarchar(MAX)  NULL,
      [maxcallbitrate] int  NULL,
      [rfc2833compensate] nvarchar(MAX)  NULL,
      [mailbox] nvarchar(MAX)  NULL,
      [session-timers] nvarchar(MAX)  NULL,
      [session-expires] int  NULL,
      [session-minse] int  NULL,
      [session-refresher] nvarchar(MAX)  NULL,
      [t38pt_usertpsource] nvarchar(MAX)  NULL,
      [regexten] nvarchar(MAX)  NULL,
      [fromdomain] nvarchar(MAX)  NULL,
      [fromuser] nvarchar(MAX)  NULL,
      [qualify] nvarchar(MAX)  NULL,
      [ip] nvarchar(MAX)  NULL,
      [rtptimeout] int  NULL,
      [rtpholdtimeout] int  NULL,
      [sendrpid] nvarchar(MAX)  NULL,
      [outboundproxy] nvarchar(MAX)  NULL,
      [callbackextension] nvarchar(MAX)  NULL,
      [registertrying] nvarchar(MAX)  NULL,
      [timert1] int  NULL,
      [timerb] int  NULL,
      [qualifyfreq] int  NULL,
      [constantssrc] nvarchar(MAX)  NULL,
      [contactpermit] nvarchar(MAX)  NULL,
      [contactdeny] nvarchar(MAX)  NULL,
      [usereqphone] nvarchar(MAX)  NULL,
      [textsupport] nvarchar(MAX)  NULL,
      [faxdetect] nvarchar(MAX)  NULL,
      [buggymwi] nvarchar(MAX)  NULL,
      [auth] nvarchar(MAX)  NULL,
      [fullname] nvarchar(MAX)  NULL,
      [trunkname] nvarchar(MAX)  NULL,
      [cid_number] nvarchar(MAX)  NULL,
      [callingpres] nvarchar(MAX)  NULL,
      [mohinterpret] nvarchar(MAX)  NULL,
      [mohsuggest] nvarchar(MAX)  NULL,
      [parkinglot] nvarchar(MAX)  NULL,
      [hasvoicemail] nvarchar(MAX)  NULL,
      [subscribemwi] nvarchar(MAX)  NULL,
      [vmexten] nvarchar(MAX)  NULL,
      [autoframing] nvarchar(MAX)  NULL,
      [rtpkeepalive] int  NULL,
      [call-limit] int   NOT NULL DEFAULT '2',
      [g726nonstandard] nvarchar(MAX)  NULL,
      [ignoresdpversion] nvarchar(MAX)  NULL,
      [allowtransfer] nvarchar(MAX)  NULL,
      [dynamic] nvarchar(MAX)  NULL,
      [partner] nvarchar(MAX)  NULL,
      [defaultuser] nvarchar(MAX)  NULL,
 )
Кодировка скуля - Cyrillic_General_CI_AS. Гугление подсказывает, что должно быть utf16
ERP система, генерирует поля fullname,name,secret,partner(для внутренних целей),regexten.
Были сделаны настройки:
res_odbc.conf

Код: Выделить всё

[SIGMA]
enabled=>yes
dsn=>SIGMA
pooling=>no
limit=>5
pre-connect=>yes
username=>userformsl
password=>passwordforsql
sanitysql => select 1
idlecheck => 600000
backslash_is_escape => no
share_connections => no
extconfig.conf

Код: Выделить всё

[settings]
sippeers => odbc,SIGMA,Numbers
func_odbc.conf (использую для проверки через консоль *)

Код: Выделить всё

[SIGMA]
dsn=SIGMA
readsql=SELECT fullname FROM numbers WHERE name='testlogin'
/etc/odbcinst.ini

Код: Выделить всё

[FreeTDS]
Description     = FreeTDS ODBC driver for MSSQL
Driver          = /usr/local/lib/libtdsodbc.so
Setup           = /usr/local/lib/libtdsodbc.so
FileUsage       = 1
Threading       = 2
/etc/odbc.ini

Код: Выделить всё

[SIGMA]
description = Asterisk ODBC for MSSQL
driver = FreeTDS
server = SIGMA
Username = userformsl
Pssword = passwordforsql
port = 1433
database = IPtel
tds_version = 7.0
language = us_english
charset = UTF8
use utf-16 = yes
Перепробовал кучу сочетаний параметров tds_version (7,0 7,1 7,2 7,4 8,0) charset (UTF8 UTF16 CP1251) use utf-16 (ставил yes no) - мою проблему это не решает
o freetds:

Код: Выделить всё

tsql -C
Compile-time settings (established with the "configure" script)
                            Version: freetds v1.00.15
             freetds.conf directory: /usr/local/etc
     MS db-lib source compatibility: no
        Sybase binary compatibility: no
                      Thread safety: yes
                      iconv library: yes
                        TDS version: auto
                              iODBC: no
                           unixodbc: yes
              SSPI "trusted" logins: no
                           Kerberos: no
                            OpenSSL: yes
                             GnuTLS: no
                               MARS: no
/usr/local/etc/freetds.conf (не уверен, что он вообще используется если freetds библиотека вызывается из unixODBC)

Код: Выделить всё

[global]
client charset = UTF-8
tds version = auto
text size = 64512
Пользователи успешно "регистрируются" и могут звонить. Но проблема с русскими символами, хранящимися в mssql базе. Таким образом у вызываемого абонента отображаются "знаки вопроса" в количестве русских символов поля fullname. Тоже самое вижу в консоли *, если добавить noop(${CALLERID(name)}).Если fullname на английском - проблем нет.
Причем если запустить тот же запрос через консоль ОС - то результат в правильной кодировке:

Код: Выделить всё

echo "SELECT fullname FROM numbers WHERE name='testlogin'" |  isql  SIGMA userformsl passwordforsql
----------------------------+
| fullname                  |
+---------------------------+
| Иван Иванов               |
+---------------------------+
SQLRowCount returns 1
Если через родную для freetds утилиту tsql - тоже всё в русской кодировке. А вот если обратиться с тем же запросом в консоле астериска через res_odbc - вот такая шляпа:

Код: Выделить всё

 odbc read ODBC_SIGMA  readsql exec
fullname              ???? ??????
Returned 1 row.  Query executed on handle 0 [SIGMA]
iconv --list выдаёт полотно кодировок. Пробовал на sql серверах (2008/2012/2014) с разным ОС (2008/2012, русифицированными/нерусифицированнами). Первоначально тип данных внутри sql был varchar, а не nvarchar, но разницы никакой. Пробовал менять locale для пользователя, запускающего астериск - не помогает. Пробовал обновить до последних стабильных версий unixODBC и freetds скомпилировав из исходников - такая же песня. Хотел было уже поставить нативный майкрософтовский клиент, но центос на котором работает * - x86, а sqlncli по-видимому требует x64 разрядность. Если конечно решение не будет найдено - перенастрою всё на х64. Но появился чисто спортивный интерес победить эту проблему.
Еще одна странная деталь. Объяснить её не могу (видимо не выучил матчасть):
Если использовать в odbc.ini параметр tds_version 7.2 7.3 7.4,то в консоле ОС строка обрезается до четырех первых символов. Для значений 7.0 7.1 8.0 - передается полностью. Но на поведение астериска изменения этих параметров не влияют. Астериск стабильно выдает все русские символы строки ввиде "знаков вопроса" .
Что еще добавить? Asterisk 11.8, Centos 6.5. Архитектурно поменять mssql на mysql не получится, ибо ERP разрабы не захотели тратить время на изучение связок с mysql. В этом я их не виню. Поэтому имеем, что имеем. Уважаемые коллеги, подскажите пожалуйста что еще можно поправить/проверить чтобы астериск увидел русскую кодировку?

Re: MS SQL, проблема с кодировкой при чтении через ODBC

Добавлено: 21 окт 2016, 12:25
ded
Если на уровне командных утилит удаётся видеть кириллицу, то почти наверняка можно получить результат и на экране абонента. Что за ИП телефоны используются? Аппаратные? Программные? Они сами точно готовы отображать кириллицу? Если да, то не мешало бы проверить: звонок между двумя абонентами Астериск с указанием кириллических Caller ID name должно подтвердить. При этом можно точно распознать тип кодировки, скорее всего нужно твёрдо в UTF-8. И если так, то, точно зная какая исходная кодировка (это можно вычислить?) можно на ходу изменять Caller ID name функцией

Код: Выделить всё

CLI> core show function ICONV 

  -= Info about function 'ICONV' =- 

[Synopsis]
Converts charsets of strings
[Description]
Converts string from <in-charset> into <out-charset>. For available charse
use 'iconv -l' on your shell command line.
NOTE: Due to limitations within the API, ICONV will not currently work wit
charsets with embedded NULLs. If found, the string will terminate.

[Syntax]
ICONV(in-charset,out-charset,string)

[Arguments]
in-charset
    Input charset
out-charset
    Output charset
string
    String to convert, from <in-charset> to <out-charset>

Re: MS SQL, проблема с кодировкой при чтении через ODBC

Добавлено: 21 окт 2016, 17:47
ronaldonn
Да. функцию iconv я тоже пробовал. Входящую кодировку подставлял всякие виды UCS2 UCS4 UTF-16 CP1251. Выходную кодировку только UTF-8 пробовал, так как полагаю, что надо ориентироваться на:

Код: Выделить всё

locale
LANG=ru_RU.utf8
По поводу телефонов. Для тестов пока использую zoiper на мобиле, и через chan_h323plus бросаю звонки на аппараты фирмы Алкатель (к сожалению алкатель на поддерживает utf8 через сип). В обоих случаях русские буквы отображаются корректно, например, если в диалплане принудительно поставить русское callerid(name).
Но у меня создалось ощущение, что астериск уже от драйвера odbc получает битую строку. Типа происходит перекодировка строки внутри функции res_odbs. Но в семплах этого модуля намеков на "encoding" я не нашел.

Re: MS SQL, проблема с кодировкой при чтении через ODBC

Добавлено: 24 окт 2016, 22:45
ded
Изучайте source
http://doxygen.asterisk.org/trunk/dd/d7 ... ource.html
и ищите С программера.

Re: MS SQL, проблема с кодировкой при чтении через ODBC

Добавлено: 31 окт 2016, 10:37
ronaldonn
С позволения модераторов спрошу в своей же теме, чтобы не создавать новую.
Конфигурация прежняя. Попробовал поставить костыль - брать те же самые поля только при помощи agi-php скрипта:

Код: Выделить всё

#!/usr/bin/php -q
<?php

set_time_limit(60);
ob_implicit_flush(false);
#error_reporting(0);

$stdin = fopen('php://stdin', 'r');
$stdout = fopen('php://stdout', 'w');
$stdlog = fopen('my_agi.log', 'w');
$agivar = array();
while (!feof($stdin)) {
$temp = fgets($stdin);
$temp = str_replace("\n","",$temp);
$s = explode(":",$temp);
if ( ! isset($s[0])) {
   $s[0] = null;
}
if ( ! isset($s[1])) {
   $s[1] = null;
}
$agivar[$s[0]] = trim($s[1]);
if (($temp == "") || ($temp == "\n")) {
break;
}
}
$login = $agivar['agi_callerid'];
$dsn='SIGMA';
$dbuser = 'userformsl';
$dbpass = 'passwordforsql';

$connect = odbc_connect($dsn,$dbuser,$dbpass);
$query = "select partner,fullname from numbers where name='$login'";
$result = odbc_exec($connect, $query); 
# print("$result \n"); 
# fetch the data from the database 

while(odbc_fetch_row($result)) {
$field1 = odbc_result($result, 1); 
$field2 = odbc_result($result, 2); 
$cname_rus = "$field1 - $field2";
//print("$cname_rus\n"); 
# print("$field1 \n"); 
} 
odbc_free_result ( $result );
# close the connection 
odbc_close($connect); 
fwrite(STDOUT,"SET VARIABLE login1  \"$login\"\n");
fflush(STDOUT);
fwrite(STDOUT,"SET VARIABLE field1  \"$field1\"\n");
fflush(STDOUT);
fwrite(STDOUT,"SET VARIABLE field2  \"$field2\"\n");
fflush(STDOUT);
fwrite(STDOUT,"SET VARIABLE cname_rus  \"$cname_rus\"\n");
fflush($stdout);
?>
Смысл в том, что зная поле name - я спрашиваю sql поля parner и fullname (переменные field1 и field2)
Запуская из астериска

Код: Выделить всё

exten => _10XX,1, Noop()
	same => n, Noop(${CALLERID(name)})
	same => n, AGI(mssql_get_cname.php)
	same => n, Set(CALLERID(name)=${myvar})
со включенным дебагом:

Код: Выделить всё

<SIP/Login10291-000005fa>AGI Tx >> agi_request: mssql_get_cname.php
<SIP/Login10291-000005fa>AGI Tx >> agi_channel: SIP/Login10291-000005fa
<SIP/Login10291-000005fa>AGI Tx >> agi_language: ru
<SIP/Login10291-000005fa>AGI Tx >> agi_type: SIP
<SIP/Login10291-000005fa>AGI Tx >> agi_uniqueid: 1477895974.1571
<SIP/Login10291-000005fa>AGI Tx >> agi_version: 11.8.1
<SIP/Login10291-000005fa>AGI Tx >> agi_callerid: Login10291
<SIP/Login10291-000005fa>AGI Tx >> agi_calleridname: CHELYSHEV
<SIP/Login10291-000005fa>AGI Tx >> agi_callingpres: 0
<SIP/Login10291-000005fa>AGI Tx >> agi_callingani2: 0
<SIP/Login10291-000005fa>AGI Tx >> agi_callington: 0
<SIP/Login10291-000005fa>AGI Tx >> agi_callingtns: 0
<SIP/Login10291-000005fa>AGI Tx >> agi_dnid: 5715
<SIP/Login10291-000005fa>AGI Tx >> agi_rdnis: unknown
<SIP/Login10291-000005fa>AGI Tx >> agi_context: BDD-INCOMING
<SIP/Login10291-000005fa>AGI Tx >> agi_extension: 1001
<SIP/Login10291-000005fa>AGI Tx >> agi_priority: 3
<SIP/Login10291-000005fa>AGI Tx >> agi_enhanced: 0.0
<SIP/Login10291-000005fa>AGI Tx >> agi_accountcode:
<SIP/Login10291-000005fa>AGI Tx >> agi_threadid: 1972693872
<SIP/Login10291-000005fa>AGI Tx >>
<SIP/Login10291-000005fa>AGI Rx << SET VARIABLE login  "Login10291"
<SIP/Login10291-000005fa>AGI Tx >> 200 result=1
<SIP/Login10291-000005fa>AGI Rx << SET VARIABLE field1  ""
<SIP/Login10291-000005fa>AGI Tx >> 200 result=1
<SIP/Login10291-000005fa>AGI Rx << SET VARIABLE field2  ""
<SIP/Login10291-000005fa>AGI Tx >> 200 result=1
<SIP/Login10291-000005fa>AGI Rx << SET VARIABLE cname_rus  ""
<SIP/Login10291-000005fa>AGI Tx >> 200 result=1
    -- <SIP/Login10291-000005fa>AGI Script mssql_get_cname.php completed, returning 0
    -- Executing [1001@BDD-INCOMING:4] Set("SIP/Login10291-00000618", "CALLERID(name)= ") in new stack
Т.е. скрипт правильно подхватывает переменную agi_callerid и возвращает обратно с именем переменной "login", а вот в переменные field1 и field2 ничего не попадает. Причем это происходит вне зависимости от того какие символы хранятся в mssql (русские или английские)
Но в то же время, если запустить этот же скрипт из консоли линукса, то всё работает:

Код: Выделить всё

# php mssql_get_cname.php
agi_callerid: Login10291

SET VARIABLE login  "Login10291"
SET VARIABLE field1  "Company"
SET VARIABLE field2  "Employee"
SET VARIABLE cname_rus  "Company - Employee"
Такому поведению объяснения найти не могу. Коллеги, не могли бы подтолкнуть куда копать?