go to post Vitaliy Serdtsev · Jan 21 @Evgeny Shvarov Unfortunately, I have not yet found a documented and [Not Internal] way out of the box to see not only the text of the request, but also the values of its input parameters. PS: take a look at the methods of the %SYSTEM.SQL.xDBC class. Here is an example of the log for the query above (from DbVisualizer): SQL text (raw from client): 1 lines line 1: SELECT * FROM del . a WHERE id < :%qpar(1) Var Types: Parameter 1: Constant of type INTEGER INPUT params: %qpar(1) = 3
go to post Vitaliy Serdtsev · Jan 21 If auditing is enabled, you can see the query without the actual parameter values. To copy the query to the SQL query tool, the query will also have to be cleaned of garbage. For example, there is the following query select * from del.a where id<3From SMP: Event: DynamicStatementQuery Event Data: SELECT * FROM del . a WHERE id < ? From DbVisualizer: Event: XDBCStatementQuery Event Data: SELECT * INTO :i%%col1, :i%%col2, :i%%col3 FROM del . a WHERE id < :%qpar(1)
go to post Vitaliy Serdtsev · Jan 21 Since you used the migration wizard, I assume that the problem is due to the primary key ID and the indexes on it. Can you use the wizard to create a test table with only two fields, ID and row_status, and post the source code of the generated class here? And more: how many records are there in the table?
go to post Vitaliy Serdtsev · Jan 21 Enable auditing for %System/%SQL/DynamicStatementQuery, %System/%SQL/EmbeddedStatementQuery, %System/%SQL/XDBCStatementQuery system events. You can view last actual values of the passed parameters of SQL query in SMP: System > Processes > Process Details > Variables
go to post Vitaliy Serdtsev · Jan 16 Rather, the discussion was about out-of-the-box equivalents of some SQL functions, so that programmers wouldn't have to reinvent the wheel. Furthermore, the routine of ^%qarfunc is undocumented. For example, making an Object Script analog of VECTOR_COSINE for integer vectors yourself is a non-trivial task, given that $VECTOROP doesn't support the integer type. Sometimes it's easier to use embedded SQL.
go to post Vitaliy Serdtsev · Jan 15 I'm curious why LPAD, RPAD and similar string functions are not exposed as $SYSTEM.SQL.Functions class members This is a good question that I would also like to get an answer to.
go to post Vitaliy Serdtsev · Jan 15 LPAD is a SQL function, not an ObjectScript function, which is why it didn't work for you if you replaced the single quotes with double quotes. USER>write ">"_LPAD(1,10,"0")_"<" >< USER>write ">"_$$lpad^%qarfunc(1,10,"0")_"<" >0000000001< There are two ways to solve the problem: using embedded SQL or an analog of LPAD for ObjectScript, for example: CREATE TABLE example_table ( id VARCHAR(10) PRIMARY KEY, normalized_id VARCHAR(10) COMPUTECODE { &sql(select LPAD(:{id}, 10, '0') into :{*}) } COMPUTEONCHANGE(id))
go to post Vitaliy Serdtsev · Jan 13 See Signing XML Documents (and especially pay attention to section "Requirements of the XML-Enabled Class")
go to post Vitaliy Serdtsev · Jan 12 This should be checked on your environment, depending on the versions of Caché, Windows, and Microsoft SQL Server used. I have already provided the code for this above. I would advise you to first make sure that you are using the latest version of Caché (and the ODBC/JDBC driver), which has fixed many bugs, including various memory leaks: Caché® and Ensemble® Change Notes (2018.1.11) In addition, you may find the New Method $SYSTEM.Util.CleanDeadJobs() useful.
go to post Vitaliy Serdtsev · Jan 9 Judging by the source code of the method EnsLib.SQL.Snapshot:ImportFromResultSet (I checked on 2025.3 CE) support for VARBINARY and LONGVARBINARY types is present.
go to post Vitaliy Serdtsev · Jan 9 Once upon a time, when the grass was greener and the sky was bluer, there was even an add-in application Caché RoseLinkPDF.
go to post Vitaliy Serdtsev · Jan 9 Here is what is in the documentation for Caché: Disable Query Timeout — Optional. If selected, causes the ODBC client driver to ignore the value of the ODBC query timeout setting. The ODBC query timeout setting specifies how long a client should wait for a specific operation to finish. If an operation does not finish within the specified time, it is automatically cancelled. The ODBC API provides functions to set this timeout value programmatically. Some ODBC applications, however, hard-code this value. If you are using an ODBC application that does not allow you to set the timeout value and the timeout value is too small, you can use the Disable Query Timeout option to disable timeouts. java.sql Exceptions The following exceptions are listed here for completeness, but are not required and are never used: ... SQLTimeoutException ... You can empirically verify all your considerations by calling a custom stored procedure from Microsoft SQL Server, for example: Class dc.a [ Abstract ] { Query DbgSqlProc( qMaxRows = 10, qTimeWait As %SmallInt = 3) As %Query(ROWSPEC = "nrow:%Integer,name:%String") [ SqlProc ] { } ClassMethod DbgSqlProcExecute( ByRef qHandle As %Binary, qMaxRows = 10, qTimeWait As %SmallInt = 3) As %Status { #define Log(%s) Set ^dbgSqlProc(qHandle("id"),%s)=$ZDateTime($NOW(),1,1,6) Set qHandle("id")=$Increment(^dbgSqlProc) Set qHandle("qMaxRows")=qMaxRows Set qHandle("qTimeWait")=qTimeWait $$$Log("1_Execute") For i=1:1:qMaxRows Set qHandle(i)=$ListBuild(i,"name"_i) Set qHandle=0 ; Hang qHandle("qTimeWait")*3 Quit $$$OK } ClassMethod DbgSqlProcFetch( ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = DbgSqlProcExecute ] { $$$Log("2_Fetch") If qHandle>qHandle("qMaxRows") { Set Row="" Set AtEnd=1 }else{ Set Row=qHandle($Increment(qHandle)) Hang qHandle("qTimeWait") } Quit $$$OK } ClassMethod DbgSqlProcClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = DbgSqlProcExecute ] { $$$Log("3_Close") Quit $$$OK } } Example of a call: select * from dc.a_DbgSqlProc(11,5) See Customized Class Queries
go to post Vitaliy Serdtsev · Jan 6 Well, of course it works. See loads data from 'C:\data\sales.csv' above.
go to post Vitaliy Serdtsev · Jan 6 Not quite so (see RowID Hidden?). ALLOWIDENTITYINSERT is needed for other purposes: IDENTITY and Counter Values Copy Data into a Duplicate Table
go to post Vitaliy Serdtsev · Jan 6 Set Up Delegated Authentication Have you enabled delegated authentication for services and applications, in particular for %Service_WebGateway?
go to post Vitaliy Serdtsev · Jan 6 dc.onetomany.PKG Class dc.onetomany.products Extends %Persistent { Parameter ALLOWIDENTITYINSERT = 1; Property name As %VarString [ SqlColumnNumber = 2 ]; Property listpriceusd As %BigInt [ SqlColumnNumber = 3 ]; Property description As %VarString [ SqlColumnNumber = 4 ]; } Class dc.onetomany.sales Extends %Persistent { Parameter ALLOWIDENTITYINSERT = 1; Property companyid As %BigInt [ SqlColumnNumber = 2 ]; Property productid As %BigInt [ SqlColumnNumber = 3 ]; Property saledate As %Date [ SqlColumnNumber = 4 ]; Property saleamountusd As %BigInt [ SqlColumnNumber = 5 ]; } Class dc.onetomany.companies Extends %Persistent { Parameter ALLOWIDENTITYINSERT = 1; Property name As %VarString [ SqlColumnNumber = 2 ]; Property industry As %VarString [ SqlColumnNumber = 3 ]; Property description As %VarString [ SqlColumnNumber = 4 ]; Property website As %VarString [ SqlColumnNumber = 5 ]; Query CompaniesBySales() As %SQLQuery(CONTAINID = 1, ROWSPEC = "id:%Integer,name:%String,industry:%String,description:%String,website:%String,totalsales:%Numeric") [ SqlName = CompaniesBySales, SqlProc ] { SELECT c.id, c.name, c.industry, c.description, c.website, SUM(s.saleamountusd) AS totalsales FROM dc_onetomany.sales s JOIN dc_onetomany.companies c ON c.id = s.companyid GROUP BY c.id, c.name, c.industry, c.description, c.website ORDER BY totalsales DESC } Query CompanySalesTotal(companyid As %Integer) As %SQLQuery(CONTAINID = 1, ROWSPEC = "companyid:%Integer,totalsales:%Numeric") [ SqlName = CompanySalesTotal, SqlProc ] { SELECT :companyid AS companyid, COALESCE(SUM(s.saleamountusd), 0) AS totalsales FROM dc_onetomany.sales s WHERE s.companyid = :companyid } /// d ##class(dc.onetomany.companies).Test() ClassMethod Test() { d ##class(dc.onetomany.sales).%KillExtent() d ##class(dc.onetomany.companies).%KillExtent() d ##class(dc.onetomany.products).%KillExtent() &sql(LOAD DATA FROM FILE 'C:\data\companies.csv' INTO dc_onetomany.companies(id,name,industry,description,website) USING {"from":{"file":{"header":true}}}) &sql(LOAD DATA FROM FILE 'C:\data\products.csv' INTO dc_onetomany.products(id,name,listPriceUSD,description) USING {"from":{"file":{"header":true}}}) &sql(LOAD DATA FROM FILE 'C:\data\sales.csv' INTO dc_onetomany.sales(ID,companyId,productId,saleDate,saleAmountUSD) VALUES (saleId,companyId,productId,saleDate,saleAmountUSD) USING {"from":{"file":{"header":true}}}) s rs = ##class(%ResultSet).%New("dc.onetomany.companies:CompanySalesTotal") d rs.Execute(103) d rs.Next() w rs.Get("totalsales")," <- 17000",! s rs = ##class(%ResultSet).%New("dc.onetomany.companies:CompanySalesTotal") d rs.Execute(104) d rs.Next() w rs.Get("totalsales")," <- 7000",! s c=##class(dc.onetomany.companies).%New() s c.name="my name 111" d c.%Save() w "current ID = ",c.%Id(),! &sql(insert into dc_onetomany.companies(ID,name) values(500,'my name 500')) w "current ID = ",%ROWID,! &sql(insert into dc_onetomany.companies(name) values('my name 501')) w "current ID = ",%ROWID,! } } USER>d ##class(dc.onetomany.companies).Test() 17000 <- 17000 7000 <- 7000 current ID = 111 current ID = 500 current ID = 501
go to post Vitaliy Serdtsev · Jan 5 Interestingly, if I try your examples, I can recreate the ambiguity error, but then get an error when attempting to specify the full param specification What is your version of IRIS and .NET? I have IRIS 2025.3CE and .NET 8.0/Framework 4.6.2 - the examples above work flawlessly and give respectively 1, 0, 1 as indicated in the comments. This is also possible: USER>s gw = $System.external.getDotNetGateway() USER>w gw.invoke("System.Convert","ToBoolean(System.UInt64)",123) 1 USER>
go to post Vitaliy Serdtsev · Jan 5 Since you manage the ID yourself, in the general case it may not necessarily be a simple counter, but, for example, a Fibonacci sequence. If the source code of the class or table is available, it is better to see how the ID is generated in it. If there is no source code, and you are sure that this is a simple counter, then in my opinion it would be easier to make a standard ID and let IRIS manage it itself.
go to post Vitaliy Serdtsev · Jan 2 var irisReference = new IRISReference(""); iris.ClassMethodStatusCode("TestClasses.TestClass", "Method1", irisReference); var pReturn = (IRISObject)irisReference.value; var list = (IRISObject)pReturn.Get("Entries"); for (var i = 1; i <= list.InvokeLong("Count"); i++) { var entry = (IRISObject)list.InvokeObject("GetAt", i); Console.WriteLine(entry.GetString("Username")); }
go to post Vitaliy Serdtsev · Jan 2 gateway.new() calls a constructor, but the System.Convert class has no constructors. To call a static method, use gateway.invoke(), but keep in mind that if you call the following code, an error will occur "Unable to resolve method overloading ambiguity": write netGate.invoke("System.Convert","ToBoolean",123)To avoid it, specify the name of the method with the full specification of the parameters, for example: s gw = $System.external.getDotNetGateway() w gw.invoke("System.Convert","ToBoolean(int)",123),! ; -> 1 w gw.invoke("System.Convert","ToBoolean(string)","false"),! ; -> 0 w gw.invoke("System.Convert","ToBoolean(string)","true"),! ; -> 1 PS: see Mapping Specification (pay special attention to the sections "Overloaded Methods" and "Restrictions")