2014年12月5日星期五

有些乱,做个进程关系的笔记

has,Oracle High Availability Services
css,  Cluster Synchronization Services
crs,  Cluster Ready Services
ons,   Oracle Notification Services
 
多了/etc/init.d/ohasd文件,这里是入口了。

crsctl start has -nowait启动了has,has后,启动css,再crs

最低层,硬件和OS
第一层,css,负责节点同步监控等
第二层,crs,我们用crsctl来操作这一层
第三层,各种资源,我们用srvctl来操作这一层,ons也是一个资源,vip等也是,asm的instance也是




2014年12月1日星期一

pdb索引表空间dbf丢失后,如何修复

在pdb里面我手动删了一个索引表空dbf,12c里恢复方法如下

[oracle@orasrv ~]$ sqlplus / as sysdba;

SQL*Plus: Release 12.1.0.1.0 Production on Mon Dec 1 20:56:43 2014

Copyright (c) 1982, 2013, Oracle.  All rights reserved.


Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, Automatic Storage Management, OLAP, Advanced Analytics
and Real Application Testing options

SQL> !rm /u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf

SQL> shutdown;
ORA-01116: error in opening database file 26
ORA-01110: data file 26: '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3
SQL> drop tablespace zyzidx including contents and datafiles;
drop tablespace zyzidx including contents and datafiles
*
ERROR at line 1:
ORA-00959: tablespace 'ZYZIDX' does not exist


SQL> alter session set container=pdborcl;

Session altered.

SQL> drop tablespace zyzidx including contents and datafiles;
drop tablespace zyzidx including contents and datafiles
*
ERROR at line 1:
ORA-01116: error in opening database file 26
ORA-01110: data file 26: '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3


SQL> alter session set container=cdb$root;

Session altered.

SQL> shutdown abort;
ORACLE instance shut down.
SQL>  startup  mount;
ORACLE instance started.

Total System Global Area 3206836224 bytes
Fixed Size                  2293496 bytes
Variable Size             771752200 bytes
Database Buffers         2415919104 bytes
Redo Buffers               16871424 bytes
Database mounted.
SQL> alter database datafile '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf' offline drop;
alter database datafile '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf' offline drop
*
ERROR at line 1:
ORA-01516: nonexistent log file, data file, or temporary file
"/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf"


SQL> alter session set container=pdborcl;

Session altered.

SQL> alter database datafile '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf' offline drop;

Database altered.

SQL> alter session set container=cdb$root;

Session altered.

SQL> alter database open;

Database altered.

SQL>  alter session set container=pdborcl;

Session altered.

SQL> drop tablespace zyzidx including contents and datafiles;
drop tablespace zyzidx including contents and datafiles
*
ERROR at line 1:
ORA-01109: database not open


SQL> alter session set container=cdb$root;

Session altered.

SQL> alter pluggable database pdborcl open;

Pluggable database altered.

SQL>  alter session set container=pdborcl;

Session altered.

SQL> drop tablespace zyzidx including contents and datafiles;

Tablespace dropped.

SQL> create tablespace zyzidx datafile '/u01/app/oracle/oradata/orcl/pdborcl/zyzidx01.dbf' size 10m;   

Tablespace created.

2014年11月27日星期四

grid的listener和oracle的listener

用了asm后,gh(grid_home)和oh(oracle_home)两边都有listener,到底该用谁的呢。我测试了一下,是不能删掉gh那边的listener的(我用的srvctl remove listener -l LISTENER),停止了后,就乱了,也就是说,gh这边的listener一定要让oracle restart去启动,不能删。当然oraclehome那边的是可以不用的。但为了方便,我两边都用,于是我修改了gh的lisener.ora。

名字修改为listenerasm,端口变成了1524,后
srvctl  stop listener
srvctl remove listener -l LISTENER
srvctl add listener -l LISTENERASM -p "TCP:1524" -o $ORACLE_HOME
另外还需要修改asm里面的local_listener参数
 以sysasm进去后,
alter system set local_listener="(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.101.81)(PORT=1524))"
然后重启后,就可以了。
crsctl stat res -t可以看到有了ora.LISTENERASM.lsn的资源,并且是online的。

到这里,好像是结束了,但是用
srvctl config asm看时,发现listenr是空的
ASM home: /u01/app/grid/product/12.1.0/grid
Password file: +DG_GRID/orapwasm
ASM listener:
Spfile: +DG_GRID/ASM/ASMPARAMETERFILE/registry.253.864659509
ASM diskgroup discovery string: /dev/raw/*

找了google,用下面的法子给填上了
 crsctl modify resource ora.asm -attr START_DEPENDENCIES="hard(ora.cssd) weak(ora.LISTENERASM.lsnr)"

然后再次执行
[grid@orasrv localhost]$ srvctl config asm
ASM home: /u01/app/grid/product/12.1.0/grid
Password file: +DG_GRID/orapwasm
ASM listener: LISTENERASM
Spfile: +DG_GRID/ASM/ASMPARAMETERFILE/registry.253.864659509
ASM diskgroup discovery string: /dev/raw/*

也可以用下面的方法

srvctl remove asm
srvctl add asm -l LISTENERASM -p +DG_GRID/ASM/ASMPARAMETERFILE/registry.253.864659509
srvctl config asm

在12.1.0.2里面这样做了,passwordfile就空了,需要通过asmcmd的pwset去设置

2014年11月26日星期三

asm的静态注册listener居然是区分大小写的

SID_LIST_LISTENER2 =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = orcl.beikan)
      (ORACLE_HOME = /u01/app/oracle/product/12.1.0/db_1)
      (SID_NAME = orcl)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = orcl2.beikan)
      (ORACLE_HOME = /u01/app/oracle/product/12.1.0/db_1)
      (SID_NAME = orcl)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = pdborcl.beikan)
      (ORACLE_HOME = /u01/app/oracle/product/12.1.0/db_1)
      (SID_NAME = orcl)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = pdborclasm.beikan)
      (ORACLE_HOME = /u01/app/oracle/product/12.1.0/db_1)
      (SID_NAME = orcl)
    )
    (SID_DESC =
      (GLOBAL_DBNAME = +ASM)
      (ORACLE_HOME = /u01/app/grid/product/12.1.0/grid)
      (SID_NAME = +ASM)
    )
  )

LISTENER2 =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1523))
      (ADDRESS = (PROTOCOL = TCP)(HOST = orasrv.beikan)(PORT = 1523))
    )
  )

我用小写启动listner2后,是连不上的。当然tnsname.ora里不区分大小写。

grid的安装

oinstall   grid, oracle     Oracle Inventory and Software Owner
dba        oralcle           Database Administrator
asmadmin   grid            Oracle Automatic Storage Management Group
asmdba     grid, oracle    ASM Database Administrator Group
asmoper    grid            ASM Operator Group
oper       oracle        Database Operator

 我碰到的问题是如果oracle不是asmdba的用户的话,dbca里面是看不见asm实例里面建立的diskgroup

而/dev/raw下面的权限,给grid:asmdba,让grid和oracle都可以读取。
 [root@orasrv rules.d]# cat /etc/udev/rules.d/60-raw.rules
# Enter raw device bindings here.
#
# An example would be:
#   ACTION=="add", KERNEL=="sda", RUN+="/bin/raw /dev/raw/raw1 %N"
# to bind /dev/raw/raw1 to /dev/sda, or
#   ACTION=="add", ENV{MAJOR}=="8", ENV{MINOR}=="1", RUN+="/bin/raw /dev/raw/raw2 %M %m"
# to bind /dev/raw/raw2 to the device with major 8, minor 1.
ACTION=="add", KERNEL=="sdb1", RUN+="/bin/raw /dev/raw/raw1 %N" 
ACTION=="add", KERNEL=="sdb2", RUN+="/bin/raw /dev/raw/raw2 %N" 
ACTION=="add", KERNEL=="sdc1", RUN+="/bin/raw /dev/raw/raw3 %N"
ACTION=="add", KERNEL=="sdc2", RUN+="/bin/raw /dev/raw/raw4 %N"
KERNEL=="raw*", OWNER="grid" GROUP="asmdba", MODE="0660"
  

# start_udev
# raw -qa
# ls -l /dev/raw
 
 
如果需要grid用户的crs能启动database,还需要将grid用户加入到dba组才可以启动,否则
[grid@orasrv ~]$ srvctl start database -d orclem
PRCR-1079 : Failed to start resource ora.orclem.db
ORA-01017: invalid username/password; logon denied
CRS-5017: The resource action "ora.orclem.db start" encountered the following error: 
ORA-01017: invalid username/password; logon denied
. For details refer to "(:CLSN00107:)" in "/u01/app/grid/diag/crs/orasrv/crs/trace/ohasd_oraagent_grid.trc".

CRS-2674: Start of 'ora.orclem.db' on 'orasrv' failed
ORA-01017: invalid username/password; logon denied
 
 

2014年11月23日星期日

oracle的平台间传送

TTS之间的传递需要convert的,应该只是endian不同的情况才需要,不同平台但endian相同,应该是不需要covnert就可以的。

如果是这样,这种情况下,应该也不需要再导出meta后 alter tablespace  read write的了

https://docs.oracle.com/cd/E15817_01/backup.111/e05700/rcmxplat.htm

2014年10月31日星期五

oracle的权限

对象权限

书上说, owner grant 对象权限1 给 A with grant option,然后A grant 给B。 A不能 revoke from B。只能ower(或者有grant any objectprivilege的user) 去 rovke from B。但是我在11g,12c里面测试,结论如下

对象权限,只能grant出去的的人去回收,就算owner或者sys都回收不回来,会报【ORA-01927: 付与していない権限にはREVOKEを実行できません。】错误,上面的B的权限只能A去revoke。当然,owner可以通过级联来回收B的权限,比如上面的owner收回A的,B的也就没了。

特殊的情况是,sys可以revoke owner分发出去的权限,比如上面sys可以回收A的权限,但是不能回收由A分发出去的B的权限。

真的是比较奇怪。

2014年10月7日星期二

audit by session & by access

11g和12c里面,对ddl只能进行by access了,执行by session会32595错。
但是,仍然可以对权限进行by session,
比如 audit select any table by zyz by session,不会报错。可是它的效果仍然是by access(一个session会产生多条记录)。 这和以前的版本是完全一些样,语句通过了,效果仍然是by access。

目前,在orcale9的文档里才有这个说明,说ddl的只能用by access。

http://docs.oracle.com/cd/B10500_01/server.920/a96524/c25audit.htm
The AUDIT statement lets you specify either BY SESSION or BY ACCESS. However, several audit options can be set only BY ACCESS, including:
  • All statement audit options that audit DDL statements
  • All privilege audit options that audit DDL statements

For all other audit options, BY SESSION is used by default.


11g
http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_4007.htm#SQLRF53733
12c
http://docs.oracle.com/database/121/SQLRF/statements_4007.htm#SQLRF55571
BY SESSION
In earlier releases, BY SESSION caused the database to write a single record for all SQL statements or operations of the same type executed on the same schema objects in the same session. Beginning with this release of Oracle Database, both BY SESSION and BY ACCESS cause Oracle Database to write one audit record for each audited statement and operation. BY SESSION continues to populate different values to the audit trail compared with BY ACCESS. Oracle recommends that you include the BY ACCESS clause for all AUDIT statements, which results in a more detailed audit record. If you specify neither clause, then BY ACCESS is the default.
Note:
This change applies only to schema object audit options, statement options and system privileges that audit SQL statements other than data definition language (DDL) statements. The database has always audited BY ACCESS all SQL statements and system privileges that audit a DDL statement.
BY ACCESS
Specify BY ACCESS if you want Oracle Database to write one record for each audited statement and operation.
Note:
If you specify either a SQL statement shortcut or a system privilege that audits a data definition language (DDL) statement, then the database always audits by access. In all other cases, the database honors the BY SESSION or BY ACCESS specification.
For statement options and system privileges that audit SQL statements other than DDL, you can specify either BY SESSION or BY ACCESS. BY ACCESS is the default.

2014年10月1日星期三

就算给了用户connect的role,仍然连接不上ORA-01045

10g以后的connect role里面就只有一个CREATE SESSION,照理如果给了用户这个role,就能连接不上,后来知道光
GRANT "CONNECT" TO user
不行
还得将这个role设置为user对的default role,也就是
alter user user default role connect
至于为啥要弄成deafult role,得看oracle的文档了
 
用户虽然有这个role,但是没enable,需要切换到这个role才能行使这个role的权限。
 
http://dba-tips.blogspot.jp/2012/03/about-default-roles.html 
 

2014年9月25日星期四

xcode 6.0.1 编译beta版本的swift项目时出link错误时

xcode 6.0.1 编译beta5版本的swift项目时出link错误时(一堆unsynbol的错误),删掉
~/Library/Developer/Xcode/DerivedData/ModuleCache
下的东西就可以,这破东西折腾我好久

2014年9月22日星期一

oracle的null

with
t as
(
select '' x from dual
union all
select null  from dual
union all
select 'a' from dual
)
select * from t
where x <> ''

这个语句,没有返回a的记录,因为''直接被转换为NULL处理了,这么想可以理解。

但是下面的语句就奇怪了

select nullif(null, 'a') x from dual
这句会报错,因为var1是null,不明确数据类型。

select nullif(to_char(null), 'a') x from dual
这句可以执行,用to_char告诉oracle是字符串

select nullif(to_number(null), 0) x from dual
这句会报错,为啥??

另外nvl(null, 0), nvl(null, 'a')都不会报错,为啥nullif却要明确指定to_char?

2014年9月15日星期一

oracle unique的约束奇怪的地方

unique约束是可以输入NULL的,比如建立一个unique的约束在列A上,那么可以输入多个A是NULL的记录。比如建立另一个约束在列A和列B上,也是可以输入多个列A和列B都是为NULL的记录。可是为什么不能输入多个列A是1(同一值),列B是NULL的记录呢?

ファイル更新時刻の精度

FAT  2.000000000  2s
ext3 1.000000000  1s
NTFS 0.000000100  100ns
XFS  0.000000001  1ns
ext4 0.000000001  1ns

2014年6月26日星期四

离开delphi,进入.net滚爬

离开delphi,进入.net滚爬,摸xamarin

2014年6月12日星期四

编译android的时候,出现E2606

写了程序,win32,ios都可以编译过了,编译android时,本来也是好好的,突然就出现了

[DCC Error] KmStatusCltMob.dpr(25): E2606 Duplicate resource: type VERSION ID 1

clean后再build仍然一样,打开res看看也没重复,奇怪了

2014年6月11日星期三

http://www.fmxexpress.com/

这要整fmx,这回应该是找到了家了吧

XE6的mobile开发到底走不走的通

进展的不顺,就回头看看到底走的方向是否正确。在开始选择方向时,就曾经反复查证,觉得是个好方向。现在想法有些变了。

一套代码,多个平台,多美好。为了实现这个,当时的比较过不少framework。基于html5的假跨平台的PhoneGap,改名后捐献给apache后叫Cordova或者Sencha,另外还有Kendo首先就排除了,嵌入浏览器形式的,还是少碰,速度应该是硬伤啊。titanium是基于js的,底层怎样走还不是很清楚,应该仍然是js的解析引擎解析后搞到本地api+本地封装的plugin api吧,也可以说是vm一种。xamarin是C#的,应该和FMX差不多了吧,可仔细看,好像也是基于vm的形式的运行的,也就是仍然call的是各自平台的api来做事,比如create a button。剩下的就FMX,真正的native,并且控件都是自己绘制,多牛。但这些想法,试着开发了这几天,才发现FMX的所谓native看起来是美妙的。

首先,速度真的那么重要吗,是否native是否真的重要吗,这有点像当年AMD说我才是真双核,Inter是假的。真假有那么重要吗,重要的是cpu真的比你快比你稳定。
第二,FMX的bug重重,用的人仍然非常少,我编译测试EMBT的自己的Sample,有些都过不去。
第三,FMX宣传的一个代码,多处使用,就连这个,也是个理想,各自平台的不同,仍然得让你准备不同的代码。
第四,EMBT的财力和人力,要死不活的状况仍然将持续很长时间,作为跟屁虫的Fans该怎么选择。EMBT目前还换汤不换药的推出了新产品AppMethod,唉,都学Adobe的CC思路了:只租不卖,穷疯了

的确是很泄气,不是Delphi的热爱者,我立刻就掉头xamarin了,因为抛出个人因素,xamarin我是更看好。如果我是一个编程初学者,xamarin应该是首选

迷茫ing...

看了这个,也得好好考虑到底“一套代码,多个平台”是否真的就是方向

最后,andorid的模拟器太慢了,可以加速的android的x86模拟器又不能用,调式iOS还得两台机器看来看去也麻烦,拜google,居然有提供租赁服务的
租赁Mac:http://www.macincloud.com
租赁Android:http://www.scirocco-cloud.com

2014年6月10日星期二

FireDac + DataSnap的mobile开发(二)

进展中,立刻又碰到另一个问题,SmartInspect是没法在iOS或者Android上面用的,那要怎样看debug log输出呢
翻来服务看帮助,可以用下面的方法来实现
procedure Log(format: String; params: array of const);
var
  l: IFMXLoggingService;
begin
  l :=  TPlatformServices.Current.GetPlatformService(IFMXLoggingService) as IFMXLoggingService;
  l.log(format, params);
end;

procedure TfrmLogin.btnOkClick(Sender: TObject);
var
  UserName, Password: string;
begin
  UserName := edtUserame.Text;
  Password := edtPassword.Text;
  if LogIn(UserName, Password) then //call ServerMethod
  begin
    Hide;
    frmMain.Show;
  end
  else begin
    Log('Wrong Password', []);
    ShowMessage('Wrong Password');
  end;
end;
运行后,iOS上面,用 模拟器 的的查看系统system.log可以看到这个的输出。在Android上,用Android Debug Monitor的logcat可以看到输出。IFMXLoggingService接口目前就一个函数。背后实现估计仍然是call的NSLog和android.util.Log。UIKit的代码跟不进去,IDE上看不见是如何实现的。

FireDac + DataSnap的mobile开发(一)

扯了一些时间,应该进入正题了。一点也不顺利。

目标是,服务器用DataSnap Server来提供http的服务,客户端可以MsWin,可以iOS,可以Android(armv7)。写代码跑了,MsWin自然是没问题,Android的虚拟机太慢了,并且EMBT目前只支持arm架构的编译。iOS跑起来还行,但是问题 仍然多多。

首先遇到的问题就是FDConnection通过DataSnap Connector上服务器时,如果使用了Filter,根本上不来,直接报错【Loading SSL module faild】,原因是Indy使用的ssl库,IdSSLOpenSSLHeaders.pas在windows上面dll可以支持,到了iOS,沙盒里,没这个库,所以就挂了。

拜google,发现这个问题大家都遇到,后来看到了这个文章,才解决

2014年6月6日星期五

在客戶端用FDConnection替代SQLConnection(二)

东摸西摸,算是搞出来了怎么使用,同时埋怨EMBT的文档质量。
在客户端用FDConnection替代SQLConnection后,用TFDStoredProc可以来访问服务器上返回DataSet的方法
服务端仍然是老代码
function TSmSample.GetTblImage: TDataSet;
var
  Conn: TFDConnection;
  Query: TFDQuery;
begin
  Conn := dmConn.AcquireConnection;
  Query := TFDQuery.Create(nil);

  try
    Query.Connection := Conn;
    Query.SQL.Text := 'SELECT * FROM tbl_image';
    Query.Active := True;
    Query.FetchAll;
    Result := TFDMemTable.Create(nil);
    TFDMemTable(Result).Data := Query.Data;  //will not trigger fetchall auto
  finally
    if Assigned(Query) then
      FreeAndNil(Query);
    dmConn.ReleaseConnection(Conn);
  end;
end;

procedure TSmSample2.Plus10(var I: Integer);
begin
   log.TrackMethod('TSmSample2.Plus10');
   I := I + 10;
end;
客户端,用FDConnection来搞就变成
procedure TForm1.Button5Click(Sender: TObject);
begin
  FDStoredProc1.Close;
  FDStoredProc1.Unprepare;
  FDStoredProc1.StoredProcName := 'TSmSample.GetTblImage';
  FDStoredProc1.Open;  //ParamByName('ReturnValue') is ptResult, a DataSet
  with FDStoredProc1 do
  while not Eof do
  begin
    log.Debug('record:%s', [FieldByName('PId').AsString]);
    Next;
  end;
  FDStoredProc1.Close;  

  FDStoredProc1.Close;
  FDStoredProc1.Unprepare;
  FDStoredProc1.StoredProcName := 'TSmSample2.Plus10';
  FDStoredProc1.Prepare;
  FDStoredProc1.ParamByName('I').Value := 20;
  FDStoredProc1.ExecProc;
  ShowMessage(inttostr(FDStoredProc1.ParamByName('I').AsInteger));  //30
  FDStoredProc1.Close;
end;
这个FDStoredProc有些像SQLConnection的SqlServerMethod,放到Form里后,点StoredProcName就访问服务器要meta信息建立下拉框了。

另外还发现的事情是,由于没走DBX,不设置DataSnapCompatibility=true,客户端也能正常显示数据,不会出现SBcdOverflow异常,FireDac的FireDAC.Comp.DataSet单元的TFDDataSet.AddFieldDesc函数里面,没有像TCustomSQLDataSet.AddFieldDesc去调整精度。另外虽然说没走dbExpress,但目前的DataSnap仍然大量用了dbx的很多unit。

在客戶端用FDConnection替代SQLConnection

今天突然留意到FireDac裡面有一個TFDPhysDSDriverLink,一查居然是DataSnap的缩写,莫非FireDac已经支持了DataSnap??莫非已经可以用FireDac的FDConnection来替代DBX的SQLConnection??是不是还是走Indy???? 有这个DataSnap的connector,但是我目前仍然没找到如何使用这个?莫非仍然开发中?如果已经可以替代SQLConnection了,那么就可以真的彻底抛弃DBX了

拜google,看到了這個,Dmitry Arefiev也明确表示了,FireDac的DataSnap Connector还是会用Indy。

2014年5月28日星期三

DataSnapCompatibility=true

测试中,当oracle定义一个字段为Number,不指定精度时,FireDac的Query拿到的时Precision和Scale都是38的TFmtBCD,而Data.DB.TFMTBCDField.Precision的定义又明确说了Precision是不能超过32的。

Precision can be a value from 0 through 32. Some database servers support more than 32 digits, but these can't be handled by client datasets, and so TFMTBCDField limits the value of Precision to be no greater than 32.

所以当服务器返回dataset到客户端后,客户端解析dataset后,也会的到一个Precision为32(服务器的38到变了32了)和Scale是38的TFMTBCDField,这个字段的值TBcd由于Scale是38超过Precision的32了,转换为其他类型比如字符串,在客户端会报Bcd OverFlow的。
追了一下在哪里将38变成32的,这是在客户端变的,代码在Data.SqlExpr的TCustomSQLDataSet.AddFieldDesc里,TCustomSQLDataSet是DBX框架里用来解析返回的TDBXReader得到DataSet的一个类。
  if FieldDesc.iFldType in [TDBXDataTypes.CurrencyType, TDBXDataTypes.BcdType] then
  begin
    FieldDesc.iUnits2 := Abs(FieldDesc.iUnits2);
    if FieldDesc.iUnits1 < FieldDesc.iUnits2 then   // iUnits1 indicates Oracle 'usable decimals'
      FieldDesc.iUnits1 := FieldDesc.iUnits2;
    // ftBCD supports only up to 18-4.  If Prec > 14 or Scale > 4, make FMTBcd
    if (FieldDesc.iUnits1 > (MaxBcdPrecision-4)) or (FieldDesc.iUnits2 > MaxBcdScale) or FNumericMapping then
    begin
      LType := ftFMTBcd;
      FieldDesc.iFldType := TDBXDataTypes.BcdType;
      if FieldDesc.iUnits1 > MaxFMTBcdDigits then //38 > 32,被强制指定为32了。
        FieldDesc.iUnits1 := MaxFMTBcdDigits;
    end;
  end;

解决的法子是,在服务器端设置FDConnection或者FDQuery也或者FDManager的DataSnapCompatibility为true,服务器的Query拿到这个字段,会设置Precision="32" Scale="12", 这样到了客户端,Precision为32,SignSpecialPlaces为12,就可以进行正常计算了。处理的代码在FireDAC.Phys里DoDefineDataTable函数里
              dtBCD,
              dtFmtBCD:
                begin
                  if oFmtOpts.DataSnapCompatibility and (rColInfo.FPrec > 32) then begin
                    if rColInfo.FPrec = rColInfo.FScale then
                      rColInfo.FScale := rColInfo.FPrec div 3; //38 div 3 = 12
                    rColInfo.FPrec := 32;
                  end;
                  oCol.Precision := rColInfo.FPrec;
                  oCol.Scale := rColInfo.FScale;
                end;
这里,可能会问,为啥服务器上TFMTBCDField.asString不会报Bcd OverFlow,因为再服务器上时Precision和Scale都是38,所以ok,看代码
function BcdToStr(const Bcd: TBcd; Format: TFormatSettings): string;
var
  Buf: array [0..66] of Char; //64 Nibbles + 1 sign + 1 decimal + #0
  PBuf: PChar;
  DecimalPos: Byte;
  I: Integer;
begin
  if Bcd.Precision = 0 then
    Exit('0');
  if (Bcd.Precision > MaxFMTBcdFractionSize) or  //MaxFMTBcdFractionSize为64,MaxFMTBcdDigits为32。这两个是嘛关系?
     ((Bcd.SignSpecialPlaces and $3F) > Bcd.Precision) then //这里关掉了SignSpecialPlaces的前两个bit后去和Precision比较,客户端因为38>32,所以异常了。
    OverflowError(SBcdOverflow);

...
有些乱,Data.DB.TFMTBCDField.Precision不能超过32,TBcd却可以到64。

2014年5月27日星期二

用TFDMemTable代替TClientDataSet

测试了TFDMemTable,的确是比以前好吃非常快的TClientDataSet还要快,那么前面写的返回数据集的就可以修改一下了
function TSmSample.GetTblImage: TDataSet;
var
  Conn: TFDConnection;
  Query: TFDQuery;
begin
  //GetInvocationMetaData.CloseSession,n := True;
  Conn := dmConn.AcquireConnection;
  Query := TFDQuery.Create(nil);

  try
    Query.Connection := Conn;
    Query.SQL.Text := 'SELECT * FROM tbl_image';
    Query.Active := True;

    Result := TFDMemTable.Create(nil);
    TFDMemTable(Result).Data := Query.Data;
    //CopyQueryToClientDataSet(Query, Result);
  finally
    if Assigned(Query) then
      FreeAndNil(Query);
    dmConn.ReleaseConnection(Conn);
  end;
end;
这里直接指定Data就可以,不必先CopyDataSet(Query, [coStructure])拷贝结构后再设置Data

给DSHTTPService追加线程缓存池2

前面写的给DSHTTPService太凌乱,后来觉得不如直接hack TIdHTTPServer类更简单,在StartUp前设置上Schedule,测试后发现可以走通,代码就清爽了很多了。
unit uIdHTTPServer;

interface

uses
  IdSchedulerOfThreadPool, IdSchedulerOfThread, IdTCPConnection, IdHTTPServer, IdScheduler,
  System.Classes;

type
  TIdHTTPServer = class(IdHTTPServer.TIdHTTPServer)
  protected
    procedure StartupPooled;
  end;


implementation

uses
  uCodeRedirect;

var
  P: TCodeRedirect;

{ TIdHTTPServer }

procedure TIdHTTPServer.StartupPooled;
var
  LScheduler: TIdScheduler;
begin
  LScheduler := Scheduler;
  if not Assigned(LScheduler) then
  begin
    LScheduler := TIdSchedulerOfThreadPool.Create(Self);
    with TIdSchedulerOfThreadPool(LScheduler) do
    begin
      MaxThreads := 100;
      PoolSize := 30;
    end;
    Scheduler := LScheduler;
  end;

  P.Disable;
  Startup; //call old Startup
  P.Enable;
end;


initialization
  P := TCodeRedirect.Create(@TIdHTTPServer.Startup, @TIdHTTPServer.StartupPooled);

finalization
  P.Free;

end.
只要包含了这个unit就行了。

2014年5月26日星期一

DataSnap是否该继续投资下去?

在众多框架里,选择学习DataSnap,是有原因的,虽然知道它问题肯定很多:慢,bug多,不能做大应用。
别的语言Grizzly, WCF,Note.js就不说了,在Pascal的世界里,有RemoteObject,有RealThinClient,还有mORMot,为啥当初选择投资DataSnap,最主要还是看中DataSnap的可以用到iOS上。
RemoteObject据说是两个人写的,以前说过,RemoteSDK不错,但是DA写的实在不怎样,再说作者也没打算发展Pascal的RO了。RealThinClient估计是一个人写的,没怎么用过。mORMot到目前为止应该还是没扩平台。考虑到投资的可持续性,选择一个公司比个人好,所以用了DataSnap。
如果但但做Windows的应用,基于http.sys的mORMot是最好的选择,开源,快而且简单。但是目前还没看见它的移动客户端。

DataSnap慢,到底慢在何处?Json的Parser是最大的杀手,其次应该是基于Indy的框架了。
对于小的应用,我感觉DataSnap还是可以的,挺好。

2014年5月25日星期日

给DSHTTPService追加线程缓存池

DSServer的start开始,DSHTTPService.Start, TDSHTTPService的FHttpServer是TDSHTTPServerIndy,TDSHTTPServerIndy的FServer是TIdHTTPServerPeer,TIdHTTPServerPeer是TIdHTTPServer的一个Peer,从而DSServer的Start开始了,TIdHTTPServer的Start。

TIdHTTPServer的Start,开始了TIdCustomTCPServer的Start,也就是正常的socket通讯开始了。这里留意的是DS一直没指定TIdCustomTCPServer的Scheduler,那么默认的Scheduler就是用了TIdSchedulerOfThreadDefault,这个是没有缓存线程的。那也就是DSHTTPService是没有线程缓存的。

咋办呢

EMBT肯定考虑到了这个问题,查看代码知道 TDSHTTPService.Start里面,
procedure TDSHTTPService.Start;
begin
  inherited;
  RequiresServer;  //如果没有,会创建
  if Assigned(FHttpServer) then
  begin
    // Moved
    //TDSHTTPServerIndy(FHttpServer).Server.UseNagle := False;
    TDSHTTPServerIndy(FHttpServer).Active := True;
  end;
end;
procedure TCustomDSRESTServerTransport.RequiresServer;
begin
  if FRestServer = nil then
  begin
    FRESTServer := CreateRESTServer; //虚的
    InitializeRESTServer;  ///虚的
  end;
end;
默认情况下,TCustomDSRESTServerTransport.Loaded是会调用RequiresServer,并走到
function TDSHTTPService.CreateHttpServer: TDSHTTPServer;
var
  LHTTPServer: TDSHTTPServerIndy;
begin
  if Assigned(FCertFiles) then
    LHTTPServer := TDSHTTPSServerIndy.Create(Self.Server, IPImplementationID)
  else
    LHTTPServer := TDSHTTPServerIndy.Create(Self.Server, IPImplementationID);
  Result := LHTTPServer;
  LHTTPServer.HTTPOtherContext := HTTPOtherContext;
end;
也就是让TDSHTTPSServerIndy来做HttpServer,也就是RestServer了。DSServer的start开始后,TDSHTTPSServerIndy的Active=True时TDSHTTPSServerIndy在父亲TDSHTTPServerIndy里面的Active里面,通过Peer才真正创建TIdHTTPServe。
但是看源码
  TIdHTTPServerIP = class(TIdHTTPServer)
  private
    FSetDestroyedProc: TProc;
  public
    destructor Destroy; override;
  end;
constructor TIdHTTPServerPeer.Create(AOwner: TComponent);
begin
  FContexts := TDictionary.Create;
  FSocketHandles := nil;
  FServerIOHandler := nil;
  FScheduler := nil;
  FOnConnectEvent := nil;
  FOnCommandGet := nil;
  FOnCommandOther := nil;
  FOnDisconnectEvent := nil;
  FOnExecuteEvent := nil;
  FHTTPServer := TIdHTTPServerIP.Create(AOwner); //这里是写死的,也就是一定要用TIdHTTPServer来做了。
  FHTTPServer.FSetDestroyedProc := SetDestroyed;
end;
那么没法子,只能在Active之前,给设置Schedule了。因而只能重载TDSHTTPServerIndy.InitializeServer了。
可是 Datasnap.DSHTTP里面, TDSHTTPServerIndy类外面是看不见的。也就无从访问TDSHTTPServerIndy.Server.SetScheduler了。
另外,TCustomDSHTTPServerTransport.HttpServer的属性,是readonly的,也是没法指定了,想给TIdHTTPServer挂上TIdSchedulerOfThreadPool好像越来越没戏

考虑用Rtti.FindType来classloader一下Datasnap.DSHTTP.TDSHTTPServerIndy类,也是不行,因为不可见,findtype返回nil。

后来找到了方法,这么做的,但是很别扭的

procedure RttiGetProperty(AClass: TClass; AInstance: TObject; PropertyName: string; var Value: TValue);
var
  ref:  TRttiContext;
  typ: TRttiType;
  mthd: TRttiMethod;
  prop: TRttiProperty;
begin
  typ := ref.GetType(AClass);
  prop := typ.GetProperty(PropertyName);
  Value := prop.GetValue(AInstance);
end;

procedure RttiSetProperty(AClass: TClass; AInstance: TObject; PropertyName: string; var Value: TValue);
var
  ref:  TRttiContext;
  typ: TRttiType;
  mthd: TRttiMethod;
  prop: TRttiProperty;
begin
  typ := ref.GetType(AClass);
  prop := typ.GetProperty(PropertyName);
  prop.SetValue(AInstance, Value);
end;

procedure TdmServer.DataModuleCreate(Sender: TObject);
  procedure SetSchedulePooled;
  var
    ref:  TRttiContext;
    class1, class2: TClass;
    obj1: TObject;  //TDSHTTPServerIndy;
    if1: IIPHTTPServer;
    obj2: TObject;  //TIdHTTPServerIP;
    V: TValue;
    //Scheduler: IIPSchedulerOfThreadPool;
    Scheduler: TIdSchedulerOfThreadPool;
  begin
    ref := TRttiContext.Create;

    obj1 := DSHTTPService.HttpServer;
    class1 := obj1.ClassType;
    RttiGetProperty(class1, obj1, 'Server', V);
    if1 := V.AsInterface as IIPHTTPServer; //TDSHTTPServerIndy.Server, IIPHTTPServer

    obj2 := if1.GetObject;
    class2 := obj2.ClassType;

    {
    Scheduler := PeerFactory.CreatePeer(IPImpId, IIPSchedulerOfThreadPool, obj2 as TComponent) as IIPSchedulerOfThreadPool;
    Scheduler.MaxThreads := 100;
    Scheduler.PoolSize := 30;
    Scheduler._AddRef;  //keep it
    V := TValue.From(Scheduler.GetObject);
    RttiSetProperty(class2, obj2, 'Scheduler', V);
    }

    Scheduler := TIdSchedulerOfThreadPool.Create(TComponent(obj2));
    Scheduler.MaxThreads := 100;
    Scheduler.PoolSize := 30;
    V := TValue.From(Scheduler);
    RttiSetProperty(class2, obj2, 'Scheduler', V);
  end;
begin
  RegisterServerLogFilter(DSTCPServerTransport.Filters);
  RegisterServerLogFilter(DSHTTPService.Filters);
  RegisterServerMethodClasss(Self, DSServer);
  DSServer.Start; //let's create TDSHTTPServerIndy(DSHTTPService.HttpServer).Server

  DSServer.Stop;
  SetSchedulePooled;
  DSServer.Start;
end;

先start再stop,就为了创建 TDSHTTPServerIndy(DSHTTPService.HttpServer).Server,很别扭。这是因为虽然可以自己创建IIPHTTPServer然后设置给TDSHTTPServerIndy.FServer,但ref.GetType(class1).GetMethod('InitializeServer')去取得TDSHTTPServerIndy的方法InitializeServer取不到,protected的方法。
这些靠RTTI来做的事情,都不过是一些淫技罢了,只能说EMBT没封装好,想扩展不能正当扩展。

DataSnap的Session

DataSnap的内存管理,太依赖于客户端发来的command,这实在是很糟糕的设计,也是DataSnap被人诟病最多的地方了:DataSnap只是玩具,没法用来做实际应用。

假设网络状况不好,或者客户端死了,比如reader_close, command_close这些命令没法传递到服务器上,也或者这些命令传递到服务器上缺丢了字节,解析失败等。DataSnap的框架目前没考虑到这些,至少目前我还没找到代码在处理这些。这些垃圾就被永远留在了服务器的内存里了。SessionTimeOut?那个不起作用的了。

吹嘘了N年,仍然没啥进步,对于24*7的服务器,内存一直飙升,最后爆表,玩完。

2014年5月20日星期二

datasnap里面用连接池

目前FireDac支持连接池,用来给Datasnap做池子,写代码测试了一下。
先写池子的函数
const
  COraclePooledName = 'Ora_Pooled';

function TdmConn.AcquireConnection: TFDConnection;
begin
  log.TrackMethod('TdmConn.AcquireConnection');
  Result := TFDConnection.Create(nil);
  Result.ConnectionDefName := COraclePooledName;
  try
    Result.Connected := True;
  except
    on E: Exception do
    begin
      FreeAndNil(Result);
      log.LogException(E);
      raise E;
    end;
  end;
end;

function TdmConn.ReleaseConnection(var Conn: TFDConnection): Boolean;
begin
  log.TrackMethod('TdmConn.ReleaseConnection');
  Result := True;
  Conn.Close;
  FreeAndNil(Conn);
end;

procedure TdmConn.DataModuleCreate(Sender: TObject);
const
  Section = 'Oracle';
var
  Params: TStringList;
begin
  Params := TStringList.Create;
  with TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')) do
  begin
    FDManager.Close;

    Params.Add(Format('Database=%s', [ReadString(Section, 'ConnStr', '')]));
    Params.Add('Pooled=True');
    Params.Add('DriverID=Ora');
    Params.Add('User_Name=xxx');
    Params.Add('Password=xxx');
    Params.Add('OSAuthent=No');
    Params.Add(Format('POOL_MaximumItems=%d', [ReadInteger(Section, 'PoolMaximumItems', 30)]));
    Params.Add(Format('POOL_CleanupTimeout=%d', [ReadInteger(Section, 'PoolCleanupTimeOut', 30000)]));
    Params.Add(Format('POOL_ExpireTimeout=%d', [ReadInteger(Section, 'PoolExpireTimeout', 90000)]));

    FDManager.AddConnectionDef(COraclePooledName, 'Ora', Params);

    Free;
  end;
  FreeAndNil(Params);
end;

procedure TdmConn.DataModuleDestroy(Sender: TObject);
begin
  FDManager.CloseConnectionDef(COraclePooledName); //close all pooled conn
  FDManager.Close;
end;
然后再使用池子
unit sSample;

interface

uses
  Data.DB, Datasnap.DBClient, Datasnap.Provider,
  FireDAC.Comp.Client, System.SysUtils,
  Data.DBXJSONReflect, System.JSON,
  uServerMethod;

type
  TSmSample = class(TServerMethod)
  public
    function ServerTime: TDateTime;
    function GetTblImage: TDataSet;
  end;

implementation

{ TSmSample }

uses
  dConn, uLog, System.StrUtils;

procedure CopyQueryToClientDataSet(SrcQuery: TDataSet; var DstQuery: TDataSet);
var
  DataSetProvider: TDataSetProvider;
begin
  try
    if DstQuery = nil then
      DstQuery := TClientDataSet.Create(nil); //will be freed by TDSServerConnectionHandler.DbxCommandClose or TDSMethodValues.AssignParameterValues 's ClearReferenceParameters
    DataSetProvider := TDataSetProvider.Create(nil);
    DataSetProvider.DataSet := SrcQuery;
    TClientDataSet(DstQuery).Data := DataSetProvider.Data;
  finally
    FreeAndNil(DataSetProvider);
  end;
end;

function TSmSample.GetTblImage: TDataSet;
var
  Conn: TFDConnection;
  Query: TFDQuery;
begin
  Conn := dmConn.AcquireConnection;
  Query := TFDQuery.Create(nil);

  try
    Query.Connection := Conn;
    Query.SQL.Text := 'SELECT * FROM tbl_image';
    Query.Active := True;
    CopyQueryToClientDataSet(Query, Result);
  finally
    if Assigned(Query) then
      FreeAndNil(Query);
    dmConn.ReleaseConnection(Conn);
  end;
end;

function TSmSample.ServerTime: TDateTime;
var
  Conn: TFDConnection;
  Query: TFDQuery;
begin
  //raise Exception.Create('hahaha');
  Conn := dmConn.AcquireConnection;
  Query := TFDQuery.Create(nil);
  try
    Query.Connection := Conn;
    Query.SQL.Text := 'SELECT sysdate FROM dual';
    Query.Active := True;
    Result := Query.FieldByName('sysdate').AsDateTime;
  finally
    FreeAndNil(Query);
    dmConn.ReleaseConnection(Conn);
  end;
end;

initialization
  RegisterServerMethodClass(TSmSample);

finalization

end.
是不是觉得挺爽,这里需要留意的是由于Conn要在服务方法内一定还给连接池,所以做的TFDQuery需要Copy给TClientDataSet这样的内存DataSet才走的通。

2014年5月13日星期二

在任意地方,得到socket的信息

用java写servlet时,随时我们都可以得到HttpServletRequest,HttpServletResponse以及session,那么在datasnap里面呢,比如我在服务里面知道当前是哪个ip在call我的服务
跟踪代码,发现session是可以得到,但是比如我打算枚举session的内容,缺没提供方法了。于是,我使用RTTI的盗窃法子,读取了私有变量
procedure RttiGetPrivateValue(AClass: TClass; AInstance: TObject; FieldName: string; var Value: TObject);
var
  ref:  TRttiContext;
  typ: TRttiType;
  mthd: TRttiMethod;
  fld: TRttiField;
begin
  typ := ref.GetType(AClass);
  fld := typ.GetField(FieldName); //can get private value
  Value := fld.GetValue(AInstance).AsObject;
end;

procedure EnumSessionData(const Session: TDSSession);
var
  SessionData: TDSSessionDictionaryData;
  MetaData: TDictionary;
  MetaObjects: TDictionary;
  Key: string;
begin
  RttiGetPrivateValue(TDSSession, Session, 'FSessionData', TObject(SessionData));
  RttiGetPrivateValue(TDSSessionDictionaryData, SessionData, 'FMetaData', TObject(MetaData));
  RttiGetPrivateValue(TDSSessionDictionaryData, SessionData, 'FMetaObjects', TObject(MetaObjects));
  for Key in MetaData.Keys do
    log.Debug('Sesion[%s] = %s', [Key, MetaData[Key]]);
  for Key in MetaObjects.Keys do
    log.Debug('SesionObjects[%s] = %s', [Key, MetaObjects[Key].ClassName]);
end;

function TdmServer.DSServerTrace(TraceInfo: TDBXTraceInfo): CBRType;
var
  Session: TDSSession;
begin
  log.TrackMethod('TdmServer.DSServerTrace');
  Session := TDSSessionManager.GetThreadSession;
  if Session.ObjectCreator <> nil then
    log.Debug(Session.ObjectCreator.ClassName);
  log.Debug(TraceInfo.Message);
  EnumSessionData(Session); //在这里试着枚举读取一下
  Result := cbrUSEDEF;
end;

可以看到,Session里面对于Tcp,默认的只有两个变量,
Sesion[remoteip] = 192.168.101.11
Sesion[communicationprotocol] = tcp/ip
这里的remoteip就是调用者ip了,但是如果想到得到sessioin对应的tunnel的具体socket信息,缺是不能了。 这两个值是在 TDSTCPServerTransport.DoOnConnect里放入的,代码如下
procedure TDSTCPServerTransport.DoOnConnect(AContext: IIPContext);
var
  IndyChannel: TDBXChannel;
  FilterChannel: TDBXFilterSocketChannel;
  Event: TDSTCPConnectEventObject;
begin
  FilterChannel := TDBXFilterSocketChannel.Create(Filters);

  IndyChannel := CreateTcpChannel(AContext);

{$IFNDEF POSIX}
  if CoInitFlags = -1 then
    CoInitializeEx(nil, COINIT_MULTITHREADED)
  else
    CoInitializeEx(nil, CoInitFlags);
{$ENDIF}
  IndyChannel.Open;

  // set the delegate
  FilterChannel.Channel := IndyChannel;

  AContext.Data := FProtocolHandlerFactory.CreateProtocolHandler(FilterChannel);
  if AContext.Data is TDBXJSonServerProtocolHandler then
  begin
    if TDBXJSonServerProtocolHandler(AContext.Data).DSSession = nil then
    begin
      TDBXJSonServerProtocolHandler(AContext.Data).DSSession :=
        TDSSessionManager.Instance.CreateSession(
          function: TDSSession
          begin
            Result := TDSTCPSession.Create(AuthenticationManager);
            Result.PutData('CommunicationProtocol', 'tcp/ip'); //这里放了进去
            Result.PutData('RemoteIP', AContext.Connection.Socket.Binding.PeerIP); //这里放了进去信息
          end,
          ''
        );
    end;
  end;

  if Assigned(FTDSTCPConnectEvent) and (AContext <> nil) and (AContext.Connection <> nil) and
    (FTcpServer <> nil) and FTcpServer.Active then
  begin
    if IndyChannel Is TDSTCPChannel then
    begin
      //enable keep alive, disable it, or leave the OS default setting as it is
      if FKeepAliveEnablement = kaEnabled then
        TDSTCPChannel(IndyChannel).EnableKeepAlive(FKeepAliveTime, FKeepAliveInterval)
      else if FKeepAliveEnablement = kaDisabled then
        TDSTCPChannel(IndyChannel).DisableKeepAlive;

      Event := TDSTCPConnectEventObject.Create(AContext.Connection.GetObject, TDSTCPChannel(IndyChannel));
    end
    else
      Event := TDSTCPConnectEventObject.Create(AContext.Connection.GetObject, nil);

    try
      OnConnect(Event);
    except
    end;
  end;
end;

留意一下key的大小写进去的是RmoteIp,出来却变成了remoteip了,所以默认的Session是不关系大小写的,这是因为TDictionary创建是并没指定TStringComparer.Ordinal。目前DataSnap还不支持自定义TDSSessionData的派生,因为TDSSession.CreateSessionData不是虚的。

2014年5月12日星期一

我写的一个注册服务方法的Unit

先定义一个基础服务方法类
unit uServerMethod;

interface

uses
  System.SysUtils, System.Classes,
  Datasnap.DSProviderDataModuleAdapter,
  Datasnap.DSServer, Datasnap.DSCommonServer, Datasnap.DSReflect;

type
  {$METHODINFO ON}
  TServerMethod = class(TPersistent)
  public
    constructor Create; virtual;
    destructor Destroy; override;
  end;
  {$METHODINFO OFF}

  TServerMethodClass = class of TServerMethod;

procedure RegisterServerMethodClass(AClass: TServerMethodClass);
procedure RegisterServerMethodClasss(AOwner: TComponent; AServer: TDSServer);

implementation

uses
  uLog;

type
  TDSServerClass = class(Datasnap.DSServer.TDSServerClass)
    private
      FServerMethodClass: TServerMethodClass;
    protected
      function GetDSClass: TDSClass; override;
      procedure CreateInstance(const CreateInstanceEventObject: TDSCreateInstanceEventObject); override;
      procedure DestroyInstance(const DestroyInstanceEventObject: TDSDestroyInstanceEventObject); override;
    public
      constructor Create(AOwner: TComponent; AServer: TDSCustomServer; AClass: TServerMethodClass); reintroduce; overload;
      destructor Destroy; override;
    end;

var
  ServerMethodList: TList;
procedure RegisterServerMethodClass(AClass: TServerMethodClass);
begin
  ServerMethodList.Add(AClass);
end;

procedure RegisterServerMethodClasss(AOwner: TComponent; AServer: TDSServer);
var
  I: Integer;
begin
  for I := 0 to ServerMethodList.Count - 1 do
  begin
    TDSServerClass.Create(AOwner, AServer, ServerMethodList[I]);
  end;
end;

{ TDSServerClass }

constructor TDSServerClass.Create(AOwner: TComponent; AServer: TDSCustomServer;
  AClass: TServerMethodClass);
begin
  inherited Create(AOwner);
  FServerMethodClass := AClass;
  Self.Server := AServer;
end;

procedure TDSServerClass.CreateInstance(
  const CreateInstanceEventObject: TDSCreateInstanceEventObject);
var
  AObject: TServerMethod;
begin
  inherited;
  if CreateInstanceEventObject.ServerClassInstance = nil then
  begin
    CreateInstanceEventObject.ServerClassInstance := FServerMethodClass.Create; //不要让TDSClass.CreateInstance去创建
  end;
end;

destructor TDSServerClass.Destroy;
begin
  inherited;
end;

procedure TDSServerClass.DestroyInstance(
  const DestroyInstanceEventObject: TDSDestroyInstanceEventObject);
begin
  inherited;

end;

function TDSServerClass.GetDSClass: TDSClass;
begin
  Result := TDSClass.Create(FServerMethodClass, False);
end;

{ TServerMethod }

constructor TServerMethod.Create;
begin
  log.Debug('%s.Create', [ClassName]);
end;

destructor TServerMethod.Destroy;
begin
  log.Debug('%s.Destroy', [ClassName]);
  inherited;
end;

initialization
  ServerMethodList := TList.Create;
finalization
  FreeAndNil(ServerMethodList);

end.
然后就可以开始继承实现做应用了,写个例子
unit sSample;

interface

uses
  uServerMethod;

type
  TSmSample = class(TServerMethod)
  public
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
  end;

implementation

{ TSmSample }

uses
  System.StrUtils;

function TSmSample.EchoString(Value: string): string;
begin
  Result := Value;
end;

function TSmSample.ReverseString(Value: string): string;
begin
  Result := System.StrUtils.ReverseString(Value);
end;

initialization
  RegisterServerMethodClass(TSmSample);

finalization

end.
以后关注服务方法实现就可以了。

TObject.Create is not a virtual method

DataSnap在创建服务方法类的时候,如果从TPersistent继承写服务方法,不写DSServerClass的DSServerClass1CreateInstance事件,是会有问题

比如我定义一个服务方法
 
  {$METHODINFO ON}
  TServerMethod = class(TPersistent)
  public
    constructor Create; virtual;
    destructor Destroy; override;
  end;
  {$METHODINFO OFF}

由于TObject.Create不是虚方法,在使用
var
  ClassRef: TTPersistentClass;
  M: TServerMethod;
begin
  ClassRef := TServerMethod;
  M := ClassRef.Create; //这里有问题,这个的Create,不会call到TServerMethod.Create,只会call TObject.Create,也就是啥也没干
end;
请看代码
function TDSServerConnectionHandler.CreateInstance(const ServerClass: TDSCustomServerClass; const DsClass: TDSClass): TObject;
var
  Instance: TObject;
begin
  if ServerClass <> nil then
  begin
    FCreateInstanceEventObject.ServerClassInstance := nil;
    ServerClass.CreateInstance(FCreateInstanceEventObject); //如果没有实现DSServerClass1CreateInstanc事件,这里的FCreateInstanceEventObject.ServerClassInstance会是nil
    Instance := FCreateInstanceEventObject.ServerClassInstance;
    if Instance <> nil then
      Exit(Instance);
  end;
  Result := DsClass.CreateInstance;//所以会走到这里
end;

function TDSClass.CreateInstance: TObject;
var
  AdapteeInstance: TObject;
  Component: TComponent;
begin
  if Assigned(FClassRef) then
  begin
    if Assigned(FAdapteeClass) then
    begin
      AdapteeInstance := FAdapteeClass.CreateInstance;
      Result := TDSAdapterClassType(FClassRef).Create(AdapteeInstance);
    end
    else
    begin
      if FClassRef.InheritsFrom(TComponent) then
      begin
        // Allows Forms and DataModules to read in the components
        // they contain.
        //
        Component := FClassRef.NewInstance as TComponent;
        Component.Create(nil);
        Result := Component;
      end
      else
        Result := FClassRef.Create//然后再走到这里,FClassRef是一个TTPersistentClass,它的Create,不会触发TServerMethod.Create
    end;
  end
  else
    Result := nil;
end;

郁闷吧,TObjec.Create不是虚方法多少年前都已经讨论过,自然有它的道理。 要解决这个问题,用上面对待TComponent方法来做可以,这样的方法其实在TApplication.CreateForm里面也用到了。
        //Result := FClassRef.Create//然后再走到这里,FClassRef是一个TTPersistentClass,它的Create,不会触发TServerMethod.Create
        //修改为,先定义一个TServerMethod的变量,再NewInstance再Create,再赋值。        

2014年5月9日星期五

FireDac的的初步2

FireDac和UniDac一样了,也支持所谓的连接池,但是限制比较多。

要支持连接池,必须让FDManager连管理,也就是,必须:1,FDConnectionDefs.ini在定义一个连接,2,必须FDConnection的Params必须是空的。第二个条件比较容易理解,因为要是不同的FDConnection指定了不同的连接参数,那么连接池里面的连接属性肯定需要不一样才行。第一个条件就比较恶心了,莫非都得需要这个配置文件才可??岂不是App都要带一个这个FDConnectionDefs.ini??

仔细看了这里

发现可以定义FireDAC supports 3 connection definition kinds:的Private类型也可以只是Pool。

代码如下了
procedure TForm1.FormCreate(Sender: TObject);
var
  oParams: TStrings;
begin
  with FDManager do
  begin
    oParams := TStringList.Create;
    oParams.Add('Database=xxx.xxx.xxx.xxx:1521/orac');
    oParams.Add('Pooled=True');
    oParams.Add('DriverID=Ora');
    oParams.Add('User_Name=username');
    oParams.Add('Password=password');
    oParams.Add('OSAuthent=No');
    oParams.Add('POOL_MaximumItems=1'); //搞一个测试看看
    FDManager.AddConnectionDef('Ora_Pooled', 'Ora', oParams);
    FreeAndNil(oParams);
  end;
end;
  
这样就可以定义一个 Private的连接了
 
另外,看了文档,也明白了,连接定义FDConnectionDefs.ini的名字可以自己起,然后设置上 
FDManagerConnectionDefFileName就可以,这样还灵活一点点。 

这个【Private】的意思,没看明白,因为就算【Persistent】也肯定是不能跨Exe来Pool的。
 

连接池的使用,直接定义一个conn,然后设置
procedure TForm1.Button1Click(Sender: TObject);
var
  conn: TFDCustomConnection;
begin
  conn := TFDCustomConnection.Create(nil);
  conn.ConnectionDefName := 'Ora_Pooled';
  FDQuery1.Connection := conn;
  FDQuery1.Active := True;

  conn := TFDCustomConnection.Create(nil);
  conn.ConnectionDefName := 'Ora_Pooled';
  FDQuery2.Connection := conn;
  FDQuery2.Active := True;
end;
启动了pool后,conn的close也不是真的close,只是还给pool。 当超过
POOL_MaximumItems定义的值时,conn的发起连接时就会报异常了。
监视oracle会发现有两个连接连接上了。
另外连接池和
  FDManager.AcquireConnection
  FDManager.ReleaseConnection
一分钱关系都没有

 

FireDAC的初步1

把AnyDac买进来后,Embarcadero重新起了个名字叫FireDAC,以前用RO时挂过AnyDAC,当时觉得deploy程序的时候,配置太繁琐了,也一直没用,因为有UniDAC的存在,AnyDac的确是既生瑜何生亮。选择UniDac的另一个原因是,UniDac对Oracle的Direct连接,不需要套Oracle客户端dll,deploy时真的时方便又快捷。

UniDac是要钱的,打算再次摸一摸FireDac,做个笔记,记录一下。另外老被吹嘘的dbExpress也算是寿终正寝了。目前FireDac的Driver是不支持DataSnap的,估计dbExpress还会保留一段时间。

基本上,我都是用Oracle,所以比较关心without oracle client的数据库连接是否能做到。

测试了几下,FireDac连接Oracle,在没有Oracle Client的情况下,是可以连接上的。方式是

前提:需要文件

    oci.dll
    oraocci11.dll
    oraociei11.dll
    orasql11.dll

这几个文件,可以放在exe同一个目录下,或者path里面能找到得到的地方。有了这4个文件,就可以连接oracle了

具体参考了这个

我不喜欢tnsname.ora的配置,因为需要外挂一个配置文件。还是Oracle easy connect string方式来的最简单直接,形式如

OraSrv:1521/orcl ,这个就和UniDac的Direct连接时指定(OraSrv:1521:orc)差不多了。