1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651
|
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
-
- This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
- project.
-
- Copyright (C) 1998-2018 OpenLink Software
-
- This project is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; only version 2 of the License, dated June 1991.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-->
<chapter label="sqlprocedures.xml" id="sqlprocedures">
<title>SQL Procedure Language Guide</title>
<abstract>
<para>Stored procedures are a key component of database performance. The fewer
messages are sent between the client and the server for a given transaction,
the faster it will complete.</para>
<para>Virtuoso/PL is a simple and straightforward language for writing stored
procedures and triggers in Virtuoso. Its syntax is a combination of SQL and
C, making learning it as easy as possible. It offers the features commonly
found in database procedure languages in a simple, efficient and concise
package. This document presents the primary concepts of the language and
ends with a reference section.</para>
</abstract>
<!-- ======================================== -->
<sect1 id="generalprinciples">
<title>General Principles</title>
<para>
A stored procedure is a named piece of Virtuoso/PL code stored in the
SYS_PROCEDURES table. Stored procedures are created with the create
procedure statement and are used by executing a procedure call statement
through the regular SQL API.
</para>
<para>
A procedure takes zero or more arguments and optionally returns a value.
Procedure arguments may be input, output or input and output.
In this manner a procedure may modify a variable passed to it by its
caller. If the procedure is called from a call statement executed by
a client process, the client process gets back the procedure's return
value and the values of output parameters.
</para>
<para>
Procedures can be called with positional or keyword parameters. A call with positional
parameters will bind the first argument in the call to the first parameter in the
procedure parameter list and so on. A keyword parameter call allows specifying
named parameters, where the argument of a given name is bound to the parameter of
the same name in the procedure's parameter list. Procedure parameters may be
required or optional. The combination of optional parameters and the keyword call
notation make it convenient to have procedures with large numbers of parameters of
which only part are used at any one time.
</para>
<para>
Procedures have local variables and cursors that are not visible to
other procedures. Procedures can call each other without limitations,
including recursively.
</para>
<para>
In addition to returning a value and changing values of output parameters
a procedure may yield one or more result sets. The client can receive
rows in result sets just like rows returned by a select statement.
A procedure calling another procedure cannot receive a result set
produced by the called procedure, however. While parameters and return
values work equally well between procedures as between procedure and
client application, a result set always goes to the client, even if the
procedure has been called by another procedure. A procedure view is a
separate construct which allows a procedure to iterate over another procedure's
result set. See the Procedure Views section.
</para>
<para>
A procedure consists of statements and expressions similar to those of any
procedural language. In addition, procedures may contain SQL statements
operating on the procedure's arguments and local variables. Writing a
stored procedure is thus much like using embedded SQL in C, except that
a stored procedure is typically much faster.
</para>
<para>
The elements of the procedure are:
</para>
<itemizedlist mark="bullet">
<listitem>
<formalpara>
<title>Procedure Declaration</title>
<para>
This is a create procedure statement that names the procedure and its
arguments.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Variable Declaration</title>
<para>
This declares a local variable for the procedure.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Cursor Declaration</title>
<para>
This declares a cursor, A cursor allows a procedure to iterate over the
rows produced by a select statement.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Manipulative SQL statement</title>
<para>
This can be a delete or update statement, either searched or positioned,
a cursor manipulation or other so called routine statement.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Control statement</title>
<para>
This is any control structure, loop, assignment or procedure call.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Handler declaration</title>
<para>
This specifies what to do in a specific exception situation. Exceptions
are error conditions produced by SQL statements (e.g. deadlock) or
'not found' situations.
</para>
</formalpara>
</listitem>
</itemizedlist>
</sect1>
<!-- ======================================== -->
<sect1 id="scopeofdeclaration">
<title>Scope of Declarations</title>
<para>A declaration can appear anywhere inside a compound statement. It affects
all statements in the compound statement following the declaration
statement.
</para>
</sect1>
<!-- ======================================== -->
<sect1 id="sqlplDATATYPES">
<title>Data Types</title>
<para>
Virtuoso/PL supports the regular SQL scalar data types as well as user-defined-types
(UDTs). UDTs and structures can be composed of data types or classes made from
any hosted language such C# or Java.
Local temporary tables are not supported by the present
Virtuoso but may be added in the future.
</para>
<para>
Memory management is automatic. Parameters, cursors and intermediate
results are automatically allocated and freed.
</para>
<para>
A parameter or variable can be of any data type a column can. Variables
are however typed at run time, so that the declared type is mostly for
documentation. The declared types also affect how interactive SQL
shows certain values.
</para>
<para>There is a special <parameter>vector</parameter> construct, declared
as type <parameter>ANY</parameter>, that can be used as an array.
Vectors must be instantiated using the <function>vector()</function> function,
optionally containing initial elements as a comma-separated list, and can
be increased in size using the <function>vector_concat()</function>.
Elements of a vector are read and changed using the
<link linkend="fn_aref"><function>aref()</function></link> and
<link linkend="fn_aset"><function>aset()</function></link> functions
respectively. A special notation can be used as a short-hand for accessing the
elements of a vector one-level deep only. The notation is by using the variable
with the index in square brackets. Hence, <function>aref(vec, 1)</function> is
the same as <function>vec[1]</function>.
The following example reveal more:</para>
<example id="ex_vectorcontrol"><title>Using Vectors</title>
<para>Simple excerpt showing how to instantiate a vector.</para>
<programlisting><![CDATA[
declare vec1, vec2, vec3 any;
vec1 := vector(); -- simple empty vector
vec2 := vector('a', 'b', 1, 2); -- vector of mixed types
vec3 := vector(vector('a', 'b'), vector(1, 2)); -- vector of vectors
]]></programlisting>
<para>Second-level elements of vec3 cannot be referenced as vec3[1][1].</para>
<para>Here is the code for a simple VSP page that shows how vectors
can be used.</para>
<programlisting><![CDATA[
<html>
<body>
<?vsp
declare vec1 any;
declare i integer;
vec1 := vector();
http('loading up the vector using vector_concat...<br>');
i := 0;
while ( i <= 5 ) {
vec1 := vector_concat(vec1, vector(i * 5));
i := i + 1;
}
http('displaying the contents using aref<br>');
i := 0;
while ( i <= 5 ) {
http_value(aref(vec1, i)); http('<br>');
i := i + 1;
}
http('changing the values using aset<br>');
i := 0;
while ( i <= 5 ) {
aset(vec1, i, i * 10);
i := i + 1;
}
http('displaying the contents using [] notation<br>');
i := 0;
while ( i <= 5 ) {
http_value(vec1[i]); http('<br>');
i := i + 1;
}
http('changing the values using [] notation<br>');
i := 0;
while ( i <= 5 ) {
vec1[i] := i * 15;
i := i + 1;
}
http('displaying the contents using [] notation again<br>');
i := 0;
while ( i <= 5 ) {
http_value(vec1[i]); http('<br>');
i := i + 1;
}
?>
</body>
</html>
]]></programlisting>
<para>Which produces the following output:</para>
<screen><![CDATA[
loading up the vector using vector_concat...
displaying the contents using aref
0
5
10
15
20
25
changing the values using aset
displaying the contents using [] notation
0
10
20
30
40
50
changing the values using [] notation
displaying the contents using [] notation again
0
15
30
45
60
75
]]></screen>
</example>
<tip><title>See Also:</title>
<para><link linkend="fn_aref"><function>aref()</function></link></para>
<para><link linkend="fn_aset"><function>aset()</function></link></para>
<para><link linkend="fn_vector_concat"><function>vector_concat()</function></link></para>
</tip>
</sect1>
<!-- ======================================== -->
<sect1 id="resultsets">
<title>Handling Result Sets</title>
<para>A single Virtuoso procedure may produce multiple result sets, each
with different result columns. A normal procedure produces one empty
result set, only returning a possible return value and values of output
parameters to the application.
</para>
<para>The <function>result_names()</function>
predefines variables to be used in a result set to follow. The variables must
be previously declared, from which the column data types are ascertained.
This assigns the meta data but does not send any results. The
<function>result()</function> function sends its parameters as a single row
of results. These parameters should be compatible with those in the previous
<function>result_names()</function>. The <function>end_results()</function>
function can be used to separate multiple result sets. The
<function>result_names()</function> can then be used to alter
the structure of the next result set.</para>
<para>The <function>result_names()</function> call can be omitted if
the application already knows what columns and their types are to be returned.</para>
<tip><title>See Also:</title>
<para><link linkend="fn_result"><function>result()</function></link>,
<link linkend="fn_result_names"><function>result_names()</function></link>,
<link linkend="fn_end_result"><function>end_result()</function></link></para>
</tip>
</sect1>
<!-- ======================================== -->
<sect1 id="arrays">
<title>Result Sets and Array Parameters</title>
<para>
A procedure may be called with array parameters,
c.f. SQLParamOptions. Each call can yield multiple result sets.
</para>
<para>
The SQLMoreResults function is used to get from one result set to the
next and from one procedure call to the next. One may have to call this
function an indeterminate number of times before all results from a
procedure with array parameters have been received.
</para>
<para>
Each procedure return is marked with SQL_SUCCESS_WITH_INFO with SQL state
'PMORE'. The next SQLFetch will retrieve the first row of the first
result set of the next procedure invocation.
</para>
</sect1>
<!-- ======================================== -->
<sect1 id="exceptions">
<title>Exception Semantics</title>
<para>
Exceptions are of two types: Not Found and SQLSTATE. A not found
exception occurs when a select - into or open statement finds no row or
when a fetch statement reads past the last row of a cursor. A SQLSTATE
exception may result from any operation, typically a manipulative
SQL statement. The SQLSTATE '4001', deadlock is an example of this.
A user-written procedure may signal a user defined exception with the
signal function.
</para>
<para>
Virtuoso/PL supports PSM 96 style exception handlers. These allow catching specific SQL states or ranges of SQL states, invoking a specific block of code when the state is signalled from within the scope of the handler. The handler may propagate the exception to an outer handler or transfer control to any appropriate point in the containing procedure.
</para>
<para>
An unhandled exception will cause the procedure where it is detected to
return the exception to its caller. If the caller is another procedure
that has a handler for the specified exception that procedure invokes the handler. If the caller is a call statement issued
by a client, the client gets the SQLSTATE and the SQLExecute function
called by the client returns SQL_ERROR and the client application may
retrieve the SQLSTATE and message with the SQLError function.
</para>
<para>
A SQLSTATE is any short string used to identify an error or exception
condition. The system itself generates certain predefined SQLSTATE's
for error conditions. Applications may add other states.
</para>
<para>
See the DECLARE HANDLER, whenever statement and signal function for an example of
exception handling.
</para>
</sect1>
<!-- ======================================== -->
<sect1 id="plref">
<title>Virtuoso/PL Syntax</title>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="createprocstmt">
<title>Create Procedure Statement</title>
<programlisting>
CREATE PROCEDURE NAME (parameter , parameter...) [RETURNS data_type]
{ statement ... }
parameter: parameter_type name data_type opt_default
parameter_type: IN | OUT | INOUT
opt_default: | DEFAULT literal | := literal
</programlisting>
<para>The create procedure statement actually performs a "create or
replace" type operation. The create procedure statement compiles and stores a
Virtuoso/PL procedure. The procedure text is first parsed and compiled into
Virtuoso virtual machine code and if the compilation is successful the text
is stored into the SYS_PROCEDURES table. This table is read at startup.
Stored procedures are thus always available for use and need be defined
only once. New procedures created with the same name as existing procedures
automatically replace their predecessor.</para>
<tip><title>See Also:</title>
<para><link linkend="createexthostproc">CREATE PROCEDURE Syntax - External hosted procedures</link></para></tip>
<screen>
CREATE PROCEDURE FIBO (IN X INTEGER)
{
IF (X < 2)
RETURN X;
ELSE
RETURN (FIBO (X - 1) + FIBO (X - 2));
}
CREATE PROCEDURE CFIBO (IN X INTEGER)
{
DECLARE RES INTEGER;
RES := FIBO (X);
RESULT_NAMES (RES);
RESULT (RES);
}
</screen>
</sect2>
<sect2 id="grantprocstmt">
<title>Grand Execute Statement</title>
<programlisting><![CDATA[
GRANT EXECUTE ON proceudre_name TO "{USER | ROLE}" ;
]]></programlisting>
<para>The identifier quote character (double quotes) is important usage information since it indicates that
the USER or ROLE has a literal identifier. Just as a reference (e.g., URL or URI) has the identifier quote
characters "<" and ">" . </para>
<para>The grantee should have SQL rights in order execution of procedure to be granted to this user.
The rights can be set from Conductor->System Admin->User Accounts->Account->Edit->User Type:</para>
<figure id="usrt" float="1">
<title>User Type</title>
<graphic fileref="ui/usrt.png"/>
</figure>
<para><emphasis>Example</emphasis></para>
<programlisting><![CDATA[
SQL>create procedure DB.DBA.SimplePrint (in txt varchar)
{
return sprintf('Output is %s', txt);
}
;
Done. -- 0 msec.
SQL>grant execute on DB.DBA.SimplePrint to "demo";
Done. -- 0 msec.
SQL>use demo;
Done. -- 0 msec.
SQL>select DB.DBA.SimplePrint('Virtuoso');
callret
VARCHAR
_______________________________________________________________________________
Output is Virtuoso
1 Rows. -- 0 msec.
]]></programlisting>
</sect2>
<sect2 id="spasviewsandtablespl">
<title>Stored Procedures as Views & Derived Tables</title>
<para>
Virtuoso allows using a stored procedure result set in place of a table. A view may also be
defined as a stored procedure. This provides smooth integration to external
procedural logic in queries.
</para>
<para>
When a procedure appears as a table, the procedure is called and its result set is inserted
into a temporary space. Processing continues from that point on as if the data came from a table.
</para>
<tip><title>See Also:</title>
<para>For more information about Store Procedures as Views & Derived Tables go to the
<link linkend="spasviewsandtables">SQL Reference Chapter</link></para></tip>
</sect2>
<sect2 id="kwds">
<title>Keyword and Optional Procedure Arguments</title>
<para>
Normally arguments in a procedure call are bound to formal parameters from left to right, as is
the default behavior in any programming language. If a default value is specified for a parameter
in the procedure definition this parameter is optional and the default value will
be assigned to it if the caller does not specify a value.
A call may consist of zero or more positional arguments followed by zero or more keyword
arguments. A positional argument is any scalar expression.
A keyword argument is marked with the syntax:
</para>
<programlisting>
NAME => scalar_exp
</programlisting>
<para>
This notation specifies that the expression is to be bound to the parameter NAME in the
procedure declaration. The names are matched case-insensitively in all case modes.
After all leading positional arguments have been bound to the matching formal parameters
in the procedure definition, each keyword argument is bound to the parameter of the same name.
After this all unbound formal parameters are assigned to their default values.
If a parameter with no default remains unbound an error is signalled. OUT and INOUT
parameters are always required, regardless of the mode of calling.
</para>
<para>
An expression can be passed as INOUT or OUT, but in that case the output
value assigned by the procedure is not accessible in the caller. The output value
is only accessible if the actual parameter is a variable or parameter.
</para>
<para>
Arguments of procedures are always evaluated left to right.
</para>
<programlisting>
create procedure kwd (in k1 int := 111, inout k2 int, in k3 int := 333)
{
result_names (k1, k2, k3);
result (k1, k2, k3);
}
kwd (1,1+1,3);
-- results 1,2,3
kwd ();
-- error because inout parameters are always required
kwd (k2=>1);
-- error because a constant is not a suitable value for an inout parameter.
kwd (k2=>1+2);
-- result 111, 2, 333
kwd (k3=>3, k1=>1,k2=>1+1);
-- result 1, 2, 3
kwd (1, k2=>1+1);
-- result 1, 2, 333
kwd (1);
-- error, k2 is required
kwd (badkey=>2, k2=>2+1);
-- error, badkey not a parameter of the function
create procedure kwd2 (in k1 int , in k2 int, in k3 int)
{
result_names (k1, k2, k3);
result (k1, k2, k3);
}
kwd2 (k1=>1, k2=>2, k3=>3);
-- result 1, 2, 3
kwd2 (1,2,3);
-- result 1, 2, 3
</programlisting>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="whilestmt">
<title>if, while, for, foreach statements</title>
<programlisting>
if_statement
: IF '(' search_condition ')' statement opt_else
opt_else
: /* empty */
| ELSE statement
while_statement
: WHILE '(' search_condition ')' statement
for_statement
: FOR '(' for_init_statement_list ';' for_opt_search_cond ';' for_inc_statement_list ')' statement
| FOREACH '(' data_type_ref identifier IN_L scalar_exp ')' DO statement
</programlisting>
<para>
The IF statement executes the immediately following statement if the
condition is true. If there is an else clause and the condition is false
the statement immediately following the else keyword will be executed.
</para>
<para>
The while statement evaluates the search condition and executes the
following statement if the condition is true. It does this as long as
the condition is true. To exit from a loop, use goto. C-like break and
continue statements are not available.
</para>
<para>
The for statement initiates the for_init_statement_list and executes
the following statement until the search condition is true. After
every execution of the statement it executes for_inc_statement_list.
You can exit the loop with using goto syntax also.
</para>
<para>
The foreach statement executes the statement for each element from
an array and sets a variable to the corresponding element of that array.
</para>
<screen><![CDATA[
IF (A > B)
A := A + 1;
ELSE
B := B + 1;
WHILE (1 = 1) {
A := A + 1;
}
FOR (declare X any, X := 1; X <= 2 ; X := X + 1){
S := S + X;
}
FOR (declare X any, X := 1; X <= 2 ; ){
S := S + X;
X := X + 1;
}
FOR (declare X any, X := 1; ; X := X + 1){
if (X > 2)
goto exit_loop;
S := S + X;
}
exit_loop:
declare X integer;
X := 1;
FOR (; X <= 2 ; X := X + 1){
S := S + X;
}
ARR := vector (1,2);
FOREACH (int X in ARR) do {
S := S + X;
}
]]></screen>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="compoundstmts">
<title>compound statement</title>
<programlisting>
compound_statement
: '{' statement_list '}'
;
statement_list
: statement_in_cs
| statement_list statement_in_cs
;
statement_in_cs
: local_declaration ';'
| compound_statement
| routine_statement ';'
| control_statement
| label ':' statement
;
statement
: routine_statement ';'
| control_statement
| compound_statement
;
local_declaration
: cursor_def
| variable_declaration
| handler_declaration
;
variable_declaration
: DECLARE variable_list data_type
;
variable_list
: NAME
| variable_list ',' NAME
;
</programlisting>
<para>The compound statement is the main building block of
procedures. Statements in a compound statement are executed left to right,
unless the flow of control is changed with a goto statement. The compound
statement allows declaring local variables and exception handlers.
See 'Scope Rules' above for a description of the scope of declarations.
</para>
<para>
Labeled statements (goto targets) and declarations can only occur within
a compound statement.
</para>
<tip>
<title>See:</title>
<para><link linkend="createprocstmt">Create Procedure statement</link></para>
</tip>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="gotortnstmts">
<title>goto, return statements</title>
<programlisting>
goto_statement
: GOTO label
;
label : NAME
return_statement
: RETURN scalar_exp
| RETURN
;
</programlisting>
<para>The goto statement unconditionally transfers control to the label
following it. The label can be anywhere within the same procedure. It is
in principle possible to jump into a block (e.g. loop body) from outside.
</para>
<para>
The return statement causes the executing procedure to return. If a
return value is specified the expression is evaluated and returned as
the return value of the procedure. If no return value is specified the
procedure returns an undefined value.
</para>
<para>
Returning from a procedure automatically frees any resources associated
with the procedure. This includes values in local variables or call by
value (IN) parameters and any cursors that may be open.
</para>
<tip>
<title>See:</title>
<para><link linkend="createprocstmt">Create Procedure statement</link></para>
</tip>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="wheneverstmt">
<title>whenever statement</title>
<programlisting>
condition
: NOT FOUND
| SQLSTATE STRING
;
handler_declaration
: WHENEVER condition GOTO NAME
;
</programlisting>
<para>This declares that control should be transferred to a particular label
in the procedure whenever a condition occurs within the lexical scope
of the WHENEVER declaration. This is similar to the statement of the
same name found in most embedded SQL implementations.
</para>
<para>
The scope of the declaration is all the lines lexically following the
declaration. A previous declaration is replaced by a new declaration
for the same <condition>.
</para>
<screen>
CREATE PROCEDURE COUNT_CUSTOMERS (IN C_NAME VARCHAR)
{
DECLARE COUNT INTEGER;
COUNT := 0;
DECLARE C CURSOR FOR SELECT C_ID FROM CUSTOMER WHERE C_NAME = C_NAME;
WHENEVER SQLSTATE '4001' GOTO DEADLOCK;
WHENEVER NOT FOUND GOTO DONE;
OPEN C;
WHILE (1=1)
{
FETCH C INTO N;
COUNT := COUNT + 1;
}
DONE:
RETURN COUNT;
DEADLOCK:
RETURN -1;
}
</screen>
<note>
<title>Note:</title>
<para>This is about the same as select count (*) from CUSTOMER where C_NAME = ?; </para>
</note>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="callassignstmt">
<title>call, assignment statements</title>
<programlisting>
function_call
: NAME '(' opt_scalar_exp_commalist ')'
| call '(' scalar_exp ')' '('opt_scalar_exp_commalist ')'
;
call_statement
: CALL NAME '(' opt_scalar_exp_commalist ')'
| function_call
;
assignment_statement
: lvalue EQUALS scalar_exp
| lvalue '=' scalar_exp
;
lvalue : NAME
</programlisting>
<para>The call statement calls a specified procedure with the given arguments.
The procedure to call is resolved at run time, i.e. the latest definition
prevails, even if it has been made after the calling procedure was
defined. The CALL reserved word is optional and is supported for
compatibility.
</para>
<para>
If the called procedure has reference parameters (OUT or INOUT) the
matching actual parameter must be a variable or parameter.
</para>
<para>
There is a computed function call form of function_call. In this,
the scalar expression in parentheses following the call keyword should
evaluate to a string which then identifies the function to be called.
</para>
<para>
The assignment statement sets a value to a variable. The variable must
be either a local variable declared with declare or a procedure argument
declared in the procedure argument list. If the variable in question is
a reference parameter the assignment takes effect in the actual parameter
as will, i.e. the value of the argument variable in the caller is set.
</para>
<screen>
CREATE PROCEDURE COMPUTED_CALL (IN Q INTEGER)
{
DECLARE FN VARCHAR;
FN := 'F';
--- CALL FUNCTION FF WITH ARGUMENT 11.
R := CALL (CONCATENATE (FN, 'F')) (11);
}
</screen>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="openfetchetcintostmts">
<title>open, fetch, close, select ... into statements</title>
<programlisting>
SELECT opt_all_distinct selection
INTO target_commalist
table_exp
with_opt_cursor_options_list
;
opt_all_distinct
: /* empty */
| ALL
| DISTINCT
;
with_opt_cursor_options_list
: /* empty */
| WITH opt_cursor_options_list
;
cursor_option
: EXCLUSIVE
;
cursor_options_commalist
: cursor_option
| cursor_options_commalist ',' cursor_option
;
opt_cursor_options_list
: /* empty */
| '(' cursor_options_commalist ')'
;
cursor_def : DECLARE NAME CURSOR FOR query_exp
| DECLARE NAME (DYNAMIC|KEYSET|STATIC) CURSOR FOR query_exp
open_statement
: OPEN cursor opt_cursor_options_list
;
fetch_statement : FETCH cursor INTO target_commalist
| FETCH cursor (FIRST|NEXT|PREVIOUS|LAST) INTO target_commalist
| FETCH cursor BOOKMARK scalar_exp INTO target_commalist
target_commalist
: variable
| target_commalist ',' variable
;
close_statement
: CLOSE cursor
;
</programlisting>
<para>The open, fetch and close statements manipulate cursors in Virtuoso/PL
statements. Cursors are declared with the declare cursor statement.
The select into statement is a shorthand for a cursor declaration, open,
fetch and close.
</para>
<para>
A forward-only cursor declaration is a declaration only and executing one does not
take time. The open statement effectively starts the search associated
with the forward-only cursor.
</para>
<para>
The forward-only cursor options used with open and select into allow controlling how
the cursor sets locks on selected rows and how many rows it fetches at
a time. The EXCLUSIVE option should be
used if intending to update or delete a row in the cursor's evaluation.
This causes selected rows to be locked with exclusive (write) locks.
</para>
<screen>
The statements:
{
DECLARE CR CURSOR FOR SELECT C_NAME FROM CUSTOMER WHERE C_ID = ID;
OPEN CR;
FETCH CR INTO NAME;
CLOSE CR;
}
and
SELECT C_NAME INTO NAME FROM CUSTOMER WHERE C_ID = ID;
have the same effect.
</screen>
<tip>
<title>See:</title>
<para>the <link linkend="tpccintro">TPC C Bench Marking</link> chapter for more examples.</para>
</tip>
</sect2>
<!-- XXX: NO INFORMATION
<sect2>
<title>result, result_names, end_result functions</title>
<para>
-->
<!-- function signal -->
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="forstmt">
<title>FOR Select Statement</title>
<programlisting>
<for statement> ::=
FOR <query exp> DO statement
</programlisting>
<para>
The FOR statement provides a compact notation for iterating over the result
set of a cursor. The body is executed once for each row in the query expression's result set.
The result columns produced by the query expression are accessible as variables of the same
name inside the body. All result columns do therefore have to be named with the AS declaration
if they are not simple columns, in which case the name defaults to the column's name.
</para>
<para>
The body can be exited in mid loop with a goto. The cursor of the FOR does not
have to be specifically closed or opened. FOR statements can be freely nested.
If a WHENEVER NOT FOUND declaration is in effect before the FOR it will be canceled
by it, so that it is not in effect after the loop's body.
</para>
<example>
<title>Examples</title>
<programlisting>
for select C_NAME, sum (O_VALUE) as value from CUSTOMER, ORDER group by C_NAME DO
{
result (C_NAME, value);
})
</programlisting>
<para>
The equivalent code is
</para>
<programlisting>
declare C_NAME, value any;
whenever not found goto done;
declare cr cursor for select ....;
open cr;
while (1) {
fetch cr into C_NAME, value;
whenever not found default;
...
}
done: ;
</programlisting>
</example>
<para>
The cursor and end label names are generated to be unique by the FOR expansion.
</para>
</sect2>
<sect2 id="setstmt">
<title>SET statement</title>
<programlisting>
Set_statement:
SET option '=' scalar_exp
| SET option OFF
| SET option ON
;
option:
ISOLATION
| LOCK_ESCALATION_PCT
| TRIGGERS
| PARAM_BATCH
;
</programlisting>
<para>The SET statement sets an option to a value. Options may control trigger
invocation, transaction isolation and other settable parameters of
the engine. A SET inside a procedure takes effect inside the procedure
and invoked procedures, counting from time of execution. Control must
pass through the SET statement for it to take effect, i.e. SET is not
a declaration. The effect of a SET does typically not persist across
procedure return.
</para>
<para>
A SET given at top level, i.e. directly executed and by a client as the
statement of a SQLExecute sets an option at the connection level. This
may only be reversed by another SET.
</para>
<para>
The option may be:
</para>
</sect2>
<sect2 id="settriggers">
<title>SET Triggers</title>
<para>
A value of OFF or 0 causes triggers not to be invoked even if there may
be applicable triggers. This is mostly useful for controlling recursion
of triggers or for debugging triggers.
</para>
<para>The value of TRIGGERS is passed into called procedures but other options are not.
</para>
<tip>
<title>See Also:</title>
<para><link linkend="setstmt">SET statement.</link></para>
</tip>
</sect2>
<sect2 id="vectoredprocedure">
<title>Vectored Procedures</title>
<para>Note: This feature only applies to Virtuoso 7.0 and later.</para>
<para>A stored procedure may be declared vectored. This means that when called from a statement
operating on multiple values, a single call of the procedure can take the whole batch of
variable bindings the statement is operating on in a single invocation. This saves invocation
and interpretation overhead, and, most importantly, allows running any SQL statements inside
the procedure on multiple values at once, creating possibilities for parallelization and
exploitation of locality. The vectored declaration consists of the VECTORED reserved
word at the start of the procedure body.</para>
<para>Consider the example of a lookup table:</para>
<programlisting><![CDATA[
CREATE TABLE person
(
p_id INT PRIMARY KEY,
p_name VARCHAR
);
CREATE TABLE knows
( p1 INT REFERENCES PERSON,
p2 INT REFERENCES PERSON,
PRIMARY KEY ( p1, p2 )
);
CREATE PROCEDURE p_name
( IN code INT )
RETURNS VARCHAR
{
VECTORED;
RETURN
(
SELECT p_name
FROM person
WHERE p_id = id
) ;
}
SELECT p_name (p1)
FROM knows
WHERE p2 = 123;
]]></programlisting>
<para>This last statement is equivalent to:</para>
<programlisting><![CDATA[
SELECT p_name
FROM knows,
person
WHERE p_id = p1
AND p2 = 123;
]]></programlisting>
<para>For non-trivial transformations, hiding the logic inside a procedure makes sense,
Running the procedure vectored makes it so that efficiency is not lost. For example,
if person 123 knows 1000 people, there will not be 1000 random lookups in person for
the names but rather a single, vectored, merge-style lookup, accessing the rows in order
of ID, saving time if the IDs are nearby each other. Furthermore, if the lookup is in
READ COMMITTED isolation, the multiple lookups can be scheduled on multiple threads.</para>
<para>The restrictions for vectored statement bodies also apply to function bodies that
are declared vectored.</para>
<para>A vectored procedure can be called from a non-vectored procedure. In this case,
the vectored procedure simply executes on a single set of values, as if it were not vectored.</para>
<para>A vectored procedure can call a non-vectored procedure. When this happens, the non-vectored
procedure is called once for each set of eligible values, i.e., once for the first values of
the arguments, once for the second values of the arguments, and so forth.</para>
<para>A vectored procedure, if called from vectored code, returns a return value for each set
of arguments.</para>
<para>A vectored procedure can have IN and OUT parameters. These have the same semantics as in
single-value execution. When calling a non-vectored procedure with an OUT or INOUT parameter,
the argument in the vectored caller must be declared to be of a boxed data type. (See the
section on vectoring and data types below.)</para>
</sect2>
<sect2 id="forvectorestatement">
<title>FOR VECTORED Statement</title>
<para>Note: This feature only applies to Virtuoso 7.0 and later.</para>
<programlisting><![CDATA[
FOR VECTORED
( {IN|OUT} <variable> <data_type> [ := <value>],
...
)
<compound_statement>
]]></programlisting>
<para>The FOR VECTORED statement allows executing a block of code on several sets of variable
bindings at once. The benefit of this is that any database operations in such a block can
be run on multiple sets of parameters at once, allowing exploitation of locality and, in some
cases, running the operation on different bindings on different threads. Additionally, if
vectored procedures are called from inside such a block, the call is made with multiple bindings
for the parameters. The input variables of FOR VECTORED are initialized from an array of
scalar values. The statements inside the body are then executed vectored, as if the operation
were first made on all the first values of the vectors, then on the second values, and so
forth. Operations combining values from different places in the vectors are not possible
in the FOR VECTORED body, but, since vectored results can be seen as arrays after the return
of FOR VECTORED, any aggregation or comparison between values in different positions of the
same vector can be done after the FOR VECTORED, simply accessing different elements of the
arrays produced.</para>
<para>The FOR VECTORED statement communicates with its environment through a list of input and
output variables. The input variables are marked with the syntax:</para>
<programlisting><![CDATA[
IN <variable> <data_type> := <value>
]]></programlisting>
<para>The <value> must be an expression evaluating to an array. The data type must correspond
to the element type of the array. When multiple input variables are specified, the arrays
initializing each must be of equal length.</para>
<para>An output variable is marked with:</para>
<programlisting><![CDATA[
OUT <variable> := <value>
]]></programlisting>
<para>The variable must be declared in a context outside of the FOR VECTORED statement. The value of
the variable will be an array where each value of the vectored expression <value> is
represented as a separate value.</para>
<para>Variables declared outside of a FOR VECTORED statement are visible in the body of FOR
VECTORED and they appear as a single value for all rows of the vectored section.</para>
<para>Consider the task of pair-wise adding the elements of two arrays:</para>
<programlisting><![CDATA[
CREATE PROCEDURE a_add
(
IN a1 INT ARRAY ,
IN a2 INT ARRAY
)
{
DECLARE res INT ARRAY ;
res := make_array (LENGTH (a1, ' any' );
FOR (i := 0; i < LENGTH (a1); i := i + 1)
res[i] := a1[i] + a2[i];
RETURN res;
}
]]></programlisting>
<para>This can be expressed as:</para>
<programlisting><![CDATA[
CREATE PROCEDURE a_add_v
(
IN a1 INT ARRAY ,
IN a2 INT ARRAY
)
{
DECLARE res INT ARRAY;
FOR VECTORED
( IN n1 INT := a1 ,
IN i2 INT := a2 ,
OUT res := r
)
{
DECLARE r INT ;
r := i1 + i2 ;
}
RETURN res;
}
]]></programlisting>
<para>The two procedures are identical in function. The second will make use of vector instructions
in the host CPU, if available, and will incur less interpretation overhead, since the SQL run
time will not need to run a loop. In practice, substantial benefit, up to an order of magnitude,
can be had from vectored execution with database operations exhibiting significant locality.
Bulk loads and bulk lookups are a typical example.</para>
</sect2>
<sect2 id="limitonvectorecode">
<title>Limitations on Vectored Code</title>
<para>Note: This feature only applies to Virtuoso 7.0 and later.</para>
<para>The body of FOR VECTORED or a vectored procedure may contain arbitrary Virtuoso PL,
except for LOOPs and backward GOTOs. Conditional expressions and statements are allowed,
as well as any subqueries or DML statements. Looping over a cursor is not allowed, since
this is a loop, but scalar subqueries and selecting-into-variables in <emphasis>SELECT ... INTO</emphasis>
is allowed. Exception handlers are not allowed inside, but an exception handler outside
of FOR VECTORED will catch errors signaled from inside FOR VECTORED. FOR VECTORED statements
may not be nested and may not occur in the body of a vectored procedure. The handler, being
itself not in vectored code, will not be able to see which specific value in a vectored
section gave rise to the exception.</para>
</sect2>
<sect2 id="datatypesandvectoring">
<title>Data Types and Vectoring</title>
<para>Note: This feature only applies to Virtuoso 7.0 and later.</para>
<para>Parameters in vectored procedures or FOR VECTORED blocks can be declared to be of the
corresponding scalar data type. The vectoring is thus in most cases transparent; the variable
will simply have multiple scalar values instead of one. The ANY type in a vectored code section
is represented as an array of serialized values. Thus types that are represented as data
structures in allocated memory (e.g., arrays, hash tables, XML elements, etc.) will not work
efficiently with ANY vectored variables. In some cases (for example, with streams or
dictionaries), assigning to a vectored ANY will lose the information.</para>
<para>Therefore, if dealing with vectors of complex data types in vectored code, the variable
holding these must be declared as an ANY ARRAY. With this type, the representation will be
an array of pointers to allocated memory, not an array of flat serialized values. The ANY
ARRAY type must be used instead of the customary ANY in all cases involving complex values
in vectored code. If dealing with vectors of simple scalars like strings or numbers, the
ANY type is generally more efficient.</para>
</sect2>
</sect1>
<sect1 id="execsqlprocselect">
<title>Execute Stored Procedures via SELECT statement</title>
<para>Stored SQL Procedures can be executed via SELECT statement:</para>
<programlisting>
SELECT PROCEDURE_NAME (parameter , parameter...);
</programlisting>
<para>For ex.:</para>
<programlisting><![CDATA[
create procedure mytest ( in ss varchar)
{
return concat('My simple test with ', ss);
}
;
SQL> select mytest('Virtuoso');
callret
VARCHAR
_______________________________________________________________________________
My simple test with Virtuoso
1 Rows. -- 0 msec.
]]></programlisting>
</sect1>
<sect1 id="execsqlprocfork">
<title>Execute Stored Procedures In Background</title>
<para>You can start procedure in background using the [name of the procedure][params]& syntax.
This feature forks another ISQL process and leaves the other on background so there will be
two separate clients running separate client connections:</para>
<programlisting><![CDATA[
SQL>create procedure test()
{
return 'my simple test';
}
;
Done. -- 0 msec.
SQL>test()&
SQL> Connected to OpenLink Virtuoso
Driver: 05.07.3033 OpenLink Virtuoso ODBC Driver
OpenLink Interactive SQL (Virtuoso), version 0.9849b.
Type HELP; for help and EXIT; to exit.
Done. -- 10 msec.
]]></programlisting>
<para>See <link linkend="asyncexecmultithread">Asynchronous Execution and Multithreading in Virtuoso/PL</link> for background jobs execution details.</para>
</sect1>
&createassembly;
&createexthostproc;
<!-- ======================================== -->
<sect1 id="asyncexecmultithread">
<title>Asynchronous Execution and Multithreading in Virtuoso/PL</title>
<para>
Many application tasks benefit from parallel execution. This is specially true of I/O intensive workloads
where each thread spends a large amount of time waiting for the network or disks. Typical tasks include
crawling the web and importing large data sets. The whole process must not stop just because there is a
file cache miss or because there is round trip latency or a name resolution delay on the net.
</para>
<para>
To this effect, Virtuoso/PL provides the async_queue object. A stored procedure may create an async_queue
that will be served by a pool of worker threads. The size of this pool can be set when creating the queue.
</para>
<para>
The thread which made the queue can use the queue to pass procedure/parameter list pairs to the threads.
If a thread is available, the thread will execute the request, if not, the next thread of the pool to
become free will take the oldest queued item and execute it. Thus the queue is served in a FIFO fashion multiplexed over n threads.
</para>
<para>
The owner of the queue can check on the results of execution either collectively or individually. Each worker thread
has its own transaction and worker threads may end up waiting for each other own database locks and can deadlock.
The worker thread code is responsible for committing its own transaction and handling any deadlock retries or such.
</para>
<para>
When the thread owning the queue makes a request, a request number is returned. This number can be used to later
request the return value and error status of the request. A queue cannot be persisted in a database table and cannot
be passed between threads. A queue can be passed between procedures and kept in data structures such as arrays.
Queue are internally reference counted and when the last reference drops the queue is freed. If a queue is freed
while there is still activity on behalf of the queue, the activities that are ongoing are finished, all requests
that are not started are discarded and all values and error states are discarded. The queue and associated
resources are thereafter freed.
</para>
<para>
There is a pool of 20 threads that are shared among all async_queues on the system. Thus, the count of threads given for
the queue is a maximum and does not guarantee that this quantity of threads be used in reality. If no thread is available
in the pool, i.e. other async queues have exhausted the entire pool, the thread making the request ends up executing the
item synchronously. One should take this possibility into account when deciding transaction boundaries, otherwise this is
transparent and the calling thread still gets a request handle and can later check for its completion.
</para>
<para>
Queues take procedures and argument lists instead of texts of SQL statements in order to save the time of compiling the text.
It is desirable for best performance to supply the name of the procedure in its full form, with full qualification and matching case.
</para>
<para>
Consider the following code samples:
</para>
<programlisting><![CDATA[
create table aqi (n int);
-- The worker procedure. Insert one row and commit.
create procedure INS1 (in n int)
{
--dbg_obj_print ('ins1 ', n);
insert into AQI (N) values (n);
commit work;
return '22';
}
create procedure taq1 (in x int, in thrs int := 1)
{
declare aq, res, err any;
declare n int;
aq := async_queue (thrs);
for (n:= 0; n < x; n:=n+1)
{
res := aq_request (aq, 'DB.DBA.INS1', vector (n));
}
return (aq_wait (aq, res, 1, err));
}
-- This procedure makes a queue with a given number of worker threads, then makes a set of requests and waits for the result
of the last one. Note that this is not necessarily the last to complete if there are multiple threads serving the queue.
]]></programlisting>
<sect2 id="synchronization">
<title>Synchronization</title>
<para>
It is possible to add requests to a queue at all times. It is also possible to check for the result of any request made so far,
by either blocking to wait for it or just checking its status.
</para>
<programlisting><![CDATA[
aq_wait (in aq any, in req_no int, in block int, out err any) returns any
]]></programlisting>
<para>
The aq_wait function takes the queue, a request number returned by aq_request, a blocking flag and an output parameter for
the error state.
</para>
<para>
If there was no error, the error state is set to 0. If the procedure was undefined, the error state is set to 2.
If there was a SQL state signalled from the procedure called on the worker thread, the error state is set to an array
of three elements: The integer 3, the SQL state string and the text of the message. If aq_wait is terminated by an external event,
then an error indicating this is signalled and the state waited for is lost. This can only happen when all transactions are
killed by shutdown or going to a single user state.
</para>
<para>
If the blocking flag was zero and the request was not complete, then the error output parameter is set to 1 and 1 is returned.
</para>
<para>
Once aq_wait has retrieved a state, the state is no longer retained in the queue.
</para>
<para>
The aq_wait_all function allows waiting for all activity to complete but discards individual return states. If some of running activities is terminated by sql error, this error will be raised in the thread executing aq_wait_all function call.
</para>
<para>
Note that it is possible to get a deadlock between the requesting thread and a worker thread and that this deadlock
cannot be detected by the database engine since this does not involve a cycle in database locks themselves. Thus,
aq_wait signals an error if the thread calling it holds database locks. Manually committing or rolling back before
calling aq_wait is necessary if the thread can belong to a transaction that holds locks.
</para>
<para>
Thus, it is most practical to explicitly commit all work on the requesting thread before calling aq_wait or aq_wait_all.
</para>
<programlisting><![CDATA[
create procedure taq_all (in x int, in thrs int := 1)
{
declare aq, res, err any;
declare n int;
aq := async_queue (thrs);
for (n:= 0; n < x; n:=n+1)
{
res := aq_request (aq, 'DB.DBA.INS1', vector (n));
}
aq_wait_all (aq);
}
-- This procedure is guaranteed to wait for all requests to be completed but will discard individual error states.
]]></programlisting>
</sect2>
</sect1>
<!-- ======================================== -->
<sect1 id="perftips">
<title>Performance Tips</title>
<sect2 id="remember">
<title>Remember the following:</title>
<itemizedlist>
<listitem>
<para>Reference parameters (inout and out) are faster then value parameters (in).</para>
</listitem>
<listitem>
<para>Use cursors and positioned delete/update (where current of) for read-modify transactions instead of a select .. into and searched update.</para>
</listitem>
<listitem>
<para>Make an EXCLUSIVE read in read-modify transactions.</para>
</listitem>
<listitem>
<para>When using ORDER BY and wishing to use a particular index, specify ALL
key parts of the index, including those that have a '=' condition. If you
want to have descending order using an index, specify DESC on ALL key parts.
</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<!-- ======================================== -->
<sect1 id="procedures_transactions">
<title>Procedures and Transactions</title>
<para>
A procedure call executed by a client is just like any other SQL
statement. It executes in the context of the client's active transaction.
If the connection is in autocommit mode the transaction is automatically
committed if the procedure returns successfully and rolled back if
the procedure returns with an error. If the connection is in manual
commit mode, a possible procedure error has no effect on the client's
transaction, unless the error is a transaction error, e.g. timeout
or deadlock.
</para>
<para>
For best performance, we recommend using procedures in autocommit
mode. In this way, a single client-server exchange will suffice to carry
out the whole transaction. This will also conveniently roll back the
transaction if the procedure exited as a result of an unhandled SQLSTATE
or a 'not found' condition.
</para>
<para>
Procedures can commit or rollback transactions using commit work and rollback work statements.
</para>
</sect1>
<!-- ======================================== -->
<!-- ======================================== -->
&twopc;
<!-- ======================================== -->
<!-- ======================================== -->
<sect1 id="triggers">
<title>Triggers</title>
<para>A trigger is a procedure body associated with a table and an event.
A trigger can take effect before, after or instead of the event on the subject
table. Several before, after or instead of triggers may exist for a given event
on a given table, which can be fired in a specified order. </para>
<para>Triggers are useful for enforcing integrity rules, maintaining the
validity of data computed from other data, accumulating history data etc.
</para>
<para>
A trigger body has no arguments in the sense a procedure does. A trigger
body implicitly sees the columns of the subject table as read-only
parameters. An update trigger may see both the new and old values of
the row of the subject table. These are differentiated by correlation
names in the REFERENCING clause.
</para>
<para>
Triggers are capable of cascading; the code of a trigger may cause
another trigger to be activated. This may lead to non-terminating
recursion in some cases. Triggers may be turned off either inside a
compound statement or inside a connection with the SET TRIGGERS OFF
statement.</para>
<para>
An update trigger may have a set of sensitive columns whose update will
cause the trigger code to be run. Update of non-sensitive columns will
not invoke the trigger. If no column list is specified any update will
invoke the trigger.
</para>
<sect2 id="createtrigger"><title>The CREATE TRIGGER statement</title>
<para>Triggers can be defined to act upon a table or column and fire upon:</para>
<simplelist>
<member>UPDATE</member>
<member>INSERT</member>
<member>DELETE</member>
</simplelist>
<para>at the following times during the operation on a table or column:</para>
<simplelist>
<member>BEFORE</member>
<member>AFTER</member>
<member>INSTEAD OF</member>
</simplelist>
<para>Triggers have a unique name which is qualified by the current catalog
and owner. The trigger name is only really relevant for the
purposes of dropping triggers. Triggers operate on a table or column which
must be adequately qualified.</para>
<para>The trigger body has read-only access to the values of the data
manipulation operation that triggered the trigger. In the case of an update
statement it has access to both old and new values for each effected column.
These values cannot be changed directly. If the trigger is to influence any
data in a table, even from the current operation, it must be achieved by
another SQL statement. The REFERENCING clause allows specifying a
correlation name for new and old values of columns. By default, the new
values are seen under the column names without a correlation name. If old
values of updated columns are needed, the REFERENCING OLD AS <alias>
will make <alias>.<column> refer to the old value.</para>
<para>Triggers defined to make further operations within the same or other
table may fire further triggers, or even the same trigger again. Care must be
taken to understand the implications of this and when triggers cane be
allowed to continue firing after the current trigger. For example, an after update
trigger that makes a further update to the same table will fire the same trigger
again and may continue looping in this way endlessly.
The <computeroutput>SET TRIGGER</computeroutput> statement can be
issued to control this:</para>
<simplelist>
<member>SET TRIGGERS on; -- (default state) further triggers within this transaction are allowed to fire.</member>
<member>SET TRIGGERS off; -- further triggers within this transaction are disabled.</member>
</simplelist>
<para>A table may have more than one trigger. Their execution order can be
specified using the ORDER clause. Each trigger gets an order number, triggers
are called starting at the lowest order number in ascending order.</para>
<para>Syntax:</para>
<programlisting>
CREATE TRIGGER NAME action_time event ON q_table_name
opt_order opt_old_ref trig_action
action_time
: BEFORE
| AFTER
event
: INSERT
| UPDATE opt_column_commalist
| DELETE
opt_order
| ORDER INTNUM
opt_old_ref
| REFERENCING old_commalist
trig_action
: compound_statement
old_commalist
: old_alias
| old_commalist ',' old_alias
old_alias
: OLD AS NAME
| NEW AS NAME
</programlisting>
<example id="ex_createtrigger"><title>Creating a simple trigger</title>
<para>This trigger is a simple example of one that would cause an
endless loop if further triggering were not disabled.</para>
<programlisting><![CDATA[
create trigger update_mydate after
update on mytable referencing old as O, new as N
{
set triggers off;
update mytable
set
previousdate = O.mydate,
mydate=now()
where id=N.id;
}
;
]]></programlisting>
<para>The trigger makes aliases for the values of the column that are part of
the SQL manipulation transaction that will be in progress, hence the values of the
columns can be accessed as "O.column" and "N.column" for old and new values respectively.</para>
<para>The set statement is scope to the procedure or trigger body where it occurs, plus procedures called from there , thus when the trigger
finishes no other triggers are effected by it.</para>
</example>
<example id="ex_createtriggerinsteadof"><title>Creating a simple trigger using INSTEAD OF</title>
<para>This trigger example will show how INSTEAD OF can be used to intercept
the values of an insert statement and re-write it. In this case the purpose is
to deliberately truncate VARCHAR inserts to prevent an error if the
data type bounds are exceeded:</para>
<para>First we create a test table with a 30 character limitation in one of the columns:</para>
<programlisting><![CDATA[
SQL>create table test_trunc (
id integer not null primary key,
txt varchar (30)
)
;
Done. -- 10 msec.
]]></programlisting>
<para>Then we attempt to insert 33 characters into it with the following results:</para>
<programlisting><![CDATA[
SQL>insert into test_trunc (id, txt)
values (1, 'aaaaaaaaaabbbbbbbbbbccccccccccxxx');
*** Error 22026: [Virtuoso ODBC Driver][Virtuoso Server]SR319: Max column length (30) of column [txt] exceeded
]]></programlisting>
<para>Now we make a trigger to fire instead of insert statements that can
perform some custom error correction, in this case we simply want to chop-off
any extra characters that will cause an insert to fail.</para>
<programlisting><![CDATA[
SQL>create trigger test_trunc_it
instead of insert on test_trunc
referencing new as N
{
set triggers off; -- we do not want this looping...
insert into test_trunc (id, txt) values (N.id, left(N.txt, 30));
}
;
Done. -- 10 msec.
]]></programlisting>
<para>We perform the same test insert, now without errors:</para>
<programlisting><![CDATA[
SQL>insert into test_trunc (id, txt)
values (1, 'aaaaaaaaaabbbbbbbbbbccccccccccxxx');
Done. -- 10 msec.
]]></programlisting>
<para>And to see what we have in the database, a quick select:</para>
<programlisting><![CDATA[
SQL> select * from test_trunc;
id txt
INTEGER NOT NULL VARCHAR
______________________________________________________
1 aaaaaaaaaabbbbbbbbbbcccccccccc
1 Rows. -- 20 msec.
]]></programlisting>
</example>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="triggers_on_views">
<title>Triggers on Views</title>
<para>In virtuoso you can create a trigger on a view. To accomplish this there is only one condition: The first trigger for a given type of event (INSERT/DELETE/UPDATE)
must be an INSTEAD OF trigger. After such a trigger is defined then any type of triggers (AFTER/BEFORE) can be added.</para>
<example id="ex_createtriggeronview"><title>Creating a trigger on view</title>
<para>We will make two tables and an union view for them. Then we will create a trigger which inserts a new record in one of the tables according to values.</para>
<para>First lets create the tables and the view.</para>
<programlisting><![CDATA[
create table first_table(
id integer not null primary key,
txt varchar
);
create table second_table(
id integer not null primary key,
txt varchar
);
create view all_tables (id,from_table,txt)
as select id,'first',txt from first_table
union all
select id,'second',txt from second_table;]]></programlisting>
<para>Now lets create a trigger instead of insert for the view and insert some data.</para>
<programlisting><![CDATA[
create trigger insert_all_tables
instead of insert on all_tables referencing new as N{
if(N.from_table = 'first' or N.from_table = 'all')
insert into first_table (id,txt) values(N.id,N.txt);
if(N.from_table = 'second' or N.from_table = 'all')
insert into second_table (id,txt) values(N.id,N.txt);
};
insert into all_tables (id,from_table,txt) values (1,'first','into first');
insert into all_tables (id,from_table,txt) values (2,'second','into second');
insert into all_tables (id,from_table,txt) values (3,'all','into all');
select * from all_tables;
id from_table txt
INTEGER VARCHAR VARCHAR
_______________________________________________________________________________
1 first into first
3 first into all
2 second into second
3 second into all
]]></programlisting>
</example>
<para>You can see that the trigger inserted the data in the two tables according the value of from_table.</para>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="droptrigger">
<title>The DROP TRIGGER statement</title>
<programlisting>
DROP TRIGGER qualified_name
</programlisting>
<para>This drops a trigger of the given name. The name may optionally have
a qualifier and owner, in which case these should be the qualifier and
owner of the subject table of the trigger. Identical trigger
names may exist for identically named tables in different namespaces.
</para>
</sect2>
<!-- ~ # ==== ~ ==== # ~ -->
<sect2 id="triggers_vdb">
<title>Triggers and Virtual Database</title>
<para>
Triggers may be defined on tables residing on remote databases. The
semantic of triggers is identical but will of course only take place when
the manipulation takes place through the Virtuoso defining the triggers.
Trigger bodies may reference remote tables just as any other procedure
bodies can. Note that triggers can be used for replication, i.e. one may
define a local change to be mirrored to a remote table using a trigger.
</para>
<para>
Consider an application with a warehouse supplying orders. There is a
total value of all orders kept at the warehouse level and there is the
total value of all order lines kept at the order level. When an order
line is added, both the order value and consequently the total order
value are updates. These values are maintained for insert, update and
delete of order line. On the other have, when an order is deleted,
all corresponding order lines must be deleted.
</para>
<para>
These rules are maintained with the below set of triggers.
</para>
<screen>
drop table T_WAREHOUSE;
drop table T_ORDER;
drop table T_ORDER_LINE;
create table T_WAREHOUSE (W_ID integer default 1,
W_ORDER_VALUE float default 0,
W_DATA varchar,
primary key (W_ID));
create table T_ORDER (O_ID integer not null primary key, O_C_ID integer,
O_W_ID integer default 1,
O_VALUE numeric default 0,
O_MODIFIED datetime);
create table T_ORDER_LINE (OL_O_ID integer,
OL_I_ID integer,
OL_QTY integer,
OL_MODIFIED timestamp,
OL_I_PRICE float default 1,
primary key (OL_O_ID, OL_I_ID));
create index OL_I_ID on T_ORDER_LINE (OL_I_ID);
create trigger AMT_INS after insert on T_ORDER_LINE
{
update T_ORDER
set O_VALUE = O_VALUE + OL_QTY * OL_I_PRICE
where O_ID = OL_O_ID;
}
create trigger AMT_DEL after delete on T_ORDER_LINE
{
update T_ORDER
set O_VALUE = O_VALUE - OL_QTY * OL_I_PRICE
where O_ID = OL_O_ID;
}
create trigger AMT before update on T_ORDER_LINE referencing old as O
{
update T_ORDER
set O_VALUE = O_VALUE - O.OL_QTY * O.OL_I_PRICE + OL_QTY * OL_I_PRICE
where O_ID = OL_O_ID;
}
create trigger W_VALUE before update (O_VALUE) on T_ORDER
referencing old as O, new as N
{
update T_WAREHOUSE
set W_ORDER_VALUE = W_ORDER_VALUE - O.O_VALUE + N.O_VALUE
where W_ID = O.O_W_ID;
}
create trigger O_DEL_OL after delete on T_ORDER order 2
{
set triggers off;
delete from T_ORDER_LINE where OL_O_ID = O_ID;
}
create trigger O_DEL_W after delete on T_ORDER order 1
{
update T_WAREHOUSE
set W_ORDER_VALUE = W_ORDER_VALUE - O_VALUE
where W_ID = O_W_ID;
}
create procedure ol_reprice_1 (in i_id integer, in i_price float)
{
declare id integer;
declare cr cursor for
select OL_I_ID from T_ORDER_LINE;
whenever not found goto done;
open cr;
while (1) {
fetch cr into id;
if (id = i_id)
update T_ORDER_LINE set OL_I_PRICE = i_price where current of cr;
}
done:
return;
}
create procedure ol_reprice_2 (in i_id integer, in i_price float)
{
declare id integer;
declare cr cursor for
select OL_I_ID from T_ORDER_LINE order by OL_I_ID;
whenever not found goto done;
open cr;
while (1) {
fetch cr into id;
if (id = i_id)
update T_ORDER_LINE set OL_I_PRICE = i_price where current of cr;
}
done:
return;
}
create procedure ol_del_i_id_2 (in i_id integer)
{
declare id integer;
declare cr cursor for
select OL_I_ID from T_ORDER_LINE order by OL_I_ID;
whenever not found goto done;
open cr;
while (1) {
fetch cr into id;
if (id = i_id)
delete from T_ORDER_LINE where current of cr;
}
done:
return;
}
</screen>
<note>
<title>Compatibility:</title>
<para>Virtuoso triggers are modeled after SQL 3. Omitted are the FOR EACH
STATEMENT and related OLD TABLE AS phrases as well as the WHEN in the
trigger body. The implementation is otherwise complete.
</para>
</note>
</sect2>
</sect1>
<!-- ~ # ==== ~ ==== # ~ -->
<sect1 id="charescaping">
<title>Character Escaping</title>
<para>
The C style escape character can be used to include special characters inside literals. The backslash character, '\', followed by an octal character code or
a special character provides a notation for characters that are normally not typable in a string literal such as tab or crlf.
Backslash support can be turned on or off at the statement level, the connection level or server default level.
</para>
<sect2 id="backslashstmt">
<title>Statement Level</title>
<para>
If you want to activate or deactivate the backslash support in a
stored procedure you can use the following two special comments (on a separate line) :
</para>
<programlisting>--no_c_escapes+</programlisting>
<para>
turns the backslash escaping support off (insert into x values ('c:\test') will result in 'c:\test' in the column
</para>
<programlisting>--no_c_escapes-</programlisting>
<para>
turns the backslash escaping support on. (same as above will insert 'c:test' in the column.)
</para>
</sect2>
<sect2 id="backslashconlevel">
<title>Connection Level</title>
<para>The switch SET SQL_NO_CHAR_C_ESCAPE can be set to 'on' or 'off' to respectively turn backslash support on or off
for the current connection</para>
<para>There is an ODBC connection attribute that can be set for the same effect in an ODBC connection.
SQLGetConnectAttr/SQLSetConnectAttr with option ID of 5002 takes values 0 or 1 to facilitate this
</para>
</sect2>
<sect2 id="backslashserver">
<title>Server Default</title>
<para>SQL_NO_CHAR_C_ESCAPE=0/1 can be set in the "Client"
section of the virtuoso.ini file to set the connection default backslash handling behavior. The default value is 0.
</para>
</sect2>
<note><title>Note</title>
<para>When a 'create procedure' is executed and that mode is "ON" the
procedure is stored in such a way that it will preserve the setting for
it's text no matter what the current default is.
</para></note>
</sect1>
<sect1 id="plscrollcrsrs">
<title>Virtuoso/PL Scrollable Cursors</title>
<para>
Virtuoso/PL supports scrollable cursors, providing functionality similar to the ODBC scrollable cursor support.
Scrollable Cursor support extends the basic (forward-only) syntax of DECLARE CURSOR and FETCH to support
the various fetch directions & cursor modes. The Virtuoso/PL scrollable cursors always operate with a
rowset size equal to 1. The keyset size (where applicable) is as per the default.
</para>
<note><title>Note:</title>
<para>If a Virtuoso/PL cursor is declared forward only it supports only FETCH .. NEXT scroll direction.
The FETCH defaults its direction to NEXT (if omitted) so this is how the syntax
extensions to DECLARE CURSOR & FETCH interoperate with the forward-only cursors syntax.
</para>
</note>
<sect2 id="PLSCROLLDECLARE">
<title>Declaring a Scrollable Cursor</title>
<para>
Virtuoso/PL cursor types are specified at declaration time. Unlike the forward-only cursor declaration
the scrollable cursor DECLARE CURSOR causes some actions (cursor statement preparation & cursor variable
assignment). The cursor variable's value can not be copied,
it should be passed only by reference in procedure calls.
Scrollable cursors have an appropriate destructor, which will close the cursor when the cursor variable goes out of scope.
Variables in the surrounding context are referenced similarly to the forward-only cursor.
</para>
<note><title>Note:</title>
<para>Some types of statements do not allow other cursor types than static.
For example SELECT DISTINCT will always result in a static cursor, ignoring the cursor declared type.
</para>
</note>
</sect2>
<sect2 id="PLSCROLLOPEN">
<title>Opening a Scrollable Cursor</title>
<para>
The OPEN on a scrollable cursor opens the cursor and sets it's position right before the first resultset row.
So before taking the bookmark value at least one FETCH should be issued.
</para>
</sect2>
<sect2 id="PLSCROLLFETCH">
<title>Fetching Data From a Scrollable Cursor</title>
<para>
The FETCH on a scrollable cursor allows specification of a direction. If there is no more data
in the specified fetch direction this causes the NOT FOUND exception to be raised, as with the
forward-only cursors. In addition to that if the row on which the cursor is about to position has been
deleted and the isolation level & cursor type allows detecting that, then the exception
SQLSTATE 'HY109' (Row deleted) is raised.
</para>
<para>
Positioning on a bookmark is done the following way:
</para>
<para>
A bookmark value should be retrieved using the <link linkend="fn_bookmark">bookmark() function</link>.
The value returned by that function can be stored, copied and retrieved.
This value can also survive a cursor close and reopen, even between transactions.
How the cursor will behave if a bookmark from a cursor with different select statement or scroll
type is used for positioning is undefined and should be avoided. On some
occasions it may signal an error, on others it will position on a wrong or non-existing row.
As a general rule bookmark values should be used only on the cursor from which they are generated.
</para>
<para>
The cursor should be in opened state. Now a FETCH .. BOOKMARK bm_value INTO ... can be issued with
the bookmark variable.
</para>
<para>
Bookmarks can serve for persisting the cursor position in an VSP context. One can imagine a VSP page
which on it's first go will execute a cursor and will show the first so-many rows.
Then it can retrieve the bookmark value of the last displayed row, persist it somehow
(for example as an HTTP session variable), then close the cursor and exit.
On each subsequent hit it will open again the same cursor, position on the bookmark
persisted and return the next, previous, first or last so-many rows.
</para>
<!-- &bookmark; -->
</sect2>
<sect2 id="PLSCROLLEXAMPLES">
<title>Virtuoso/PL Scrollable Cursor Examples</title>
<example>
<title>Procedure using scrollable cursor to read the keys in batches of 20</title>
<programlisting><![CDATA[
create procedure READ_KEYS_NEXT_20 (in mask varchar, inout bm any) returns integer
{
--- This procedure reads the next 20 table names based on a bookmark value.
declare cr static cursor for
select distinct KEY_TABLE
from DB.DBA.SYS_KEYS
where
KEY_IS_MAIN = 1 and
KEY_MIGRATE_TO is NULL and
KEY_TABLE like mask;
declare table_name varchar;
declare inx integer;
inx := 1;
-- no 'Row Deleted' (HY109) handling as the static cursors doesn't show the deleted rows.
whenever not found goto done;
open cr;
-- positions on the bookmark or on the first if it is null
-- and fetches the value into table_name
if (bm is not null)
{
fetch cr bookmark bm into table_name;
-- note that the value from fetch bookmark is omitted
fetch cr next into table_name;
}
else
fetch cr first into table_name;
-- fetches the next 20 rows (or less)
while (inx < 20)
{
result (table_name);
inx := inx + 1;
fetch cr next into table_name;
}
-- 20 rows were fetched - get the bookmark of the last row fetched
bm := bookmark (cr);
close cr;
return;
done:
-- no more rows - set the bookmark to NULL
close cr;
bm := NULL;
};
create procedure READ_KEYS (in mask varchar)
{
-- the main function (mask is a mask to be applied over the select
declare table_name varchar;
declare bm any;
-- it'll return a resultset with a single column
result_names (table_name);
-- sets the bookmark to empty
bm := NULL;
while (1)
{
READ_KEYS_NEXT_20 (mask, bm);
-- that's the flag for no more rows
if (bm is NULL)
return;
}
};
READ_KEYS ('%');
]]></programlisting>
</example>
</sect2>
<sect2 id="PLFORWARDONLYCR">
<title>FORWARD-ONLY (traditional cursor statement) Example</title>
<example>
<title>Procedure using forward only cursor</title>
<programlisting><![CDATA[
create procedure TEST_FW()
{
declare cr cursor for select KEY_ID from DB.DBA.SYS_KEYS;
declare inx, data integer;
inx := 0;
whenever not found goto done;
open cr;
while (1)
{
fetch cr into data;
inx := inx + 1;
}
done:
close cr;
result_names (data);
result (inx);
};
TEST_FW();
]]></programlisting>
</example>
</sect2>
<sect2 id="PLDYNAMICCR">
<title>DYNAMIC (traditional cursor statement) Example</title>
<example>
<title>Procedure using dynamic cursor</title>
<programlisting><![CDATA[
create procedure TEST_DYNAMIC ()
{
declare cr dynamic cursor for select KEY_ID from DB.DBA.SYS_KEYS;
declare inx, data integer;
inx := 0;
whenever not found goto done;
open cr;
while (1)
{
fetch cr into data;
inx := inx + 1;
}
done:
close cr;
result_names (data);
result (inx);
};
TEST_DYNAMIC ();
]]></programlisting>
</example>
</sect2>
<sect2 id="PLKEYSETCR">
<title>KEYSET (traditional cursor statement) Example</title>
<example>
<title>Procedure using keyset cursor</title>
<programlisting><![CDATA[
create procedure TEST_KEYSET ()
{
declare cr keyset cursor for select KEY_ID from DB.DBA.SYS_KEYS;
declare inx, data integer;
inx := 0;
whenever not found goto done;
open cr;
while (1)
{
fetch cr into data;
inx := inx + 1;
}
done:
close cr;
result_names (data);
result (inx);
};
TEST_KEYSET ();
]]></programlisting>
</example>
</sect2>
</sect1>
<sect1 id="plmodules"><title>Virtuoso PL Modules</title>
<para>
Modules are packages of procedures which compile together. Procedure names in
module definitions are not fully qualified names, but consist only of a single
identifier that it is appended to the name of the module (which is a 3-part
name) to make the 4-part module procedure name.
</para>
<para>
Module procedures do not appear in SQLProcedures output. Module names are in
the same domain as the procedure names, so it is not possible to have
a procedure with the same name as an existing module.
</para>
<sect2 id="plmodsyntax"><title>Syntax</title>
<programlisting>
CREATE MODULE
m_name
{
[PROCEDURE|FUNCTION] p_name1 (...) { ...};
[PROCEDURE|FUNCTION] p_name2 (...) { ...};
...
[PROCEDURE|FUNCTION] p_nameN (...) { ...};
}
</programlisting>
<programlisting>
DROP MODULE m_name;
</programlisting>
</sect2>
<example id="ex_plmod2"><title>Procedure Modules</title>
<programlisting>
create module
DB.DBA.MOD
{
function MOD1 () returns varchar {
return ('MOD1');
};
procedure MOD2 () {
return concat (MOD1(), 'MOD2');
};
};
</programlisting>
<para>
This example creates a module, MOD, with 2 procedures: MOD1 & MOD2.
Their fully-qualified names are DB.DBA.MOD.MOD1 and DB.DBA.MOD.MOD2.
</para>
<para>
Note the call to MOD1 in MOD2 - it is not fully qualified, but it resolves to
the module procedure MOD1, instead of any procedure external to the module.
</para>
</example>
<para>
A single part procedure name in a call inside a module is first matched against procedures defined in the module.
If the above example were executed by DBA (in the DB qualifier),
then the below statements are equivalent:
</para>
<programlisting>
select DB.DBA.MOD.MOD1()
select DB..MOD.MOD1()
</programlisting>
<para>
The statement:
</para>
<programlisting>
select MOD.MOD1()
</programlisting>
<para>
will result in calling the DB.DBA.MOD.MOD1() only if a function DB.MOD.MOD1
does not exist. If it exists, it will be preferred over DB.DBA.MOD.MOD1
when using this notation.
</para>
<sect2 id="plmodsecurity"><title>Security</title>
<para>
Module procedures can be granted to users. Modules can also be granted to
users. Granting execute to a module is equivalent to granting execute for
all of the module's procedures.</para>
</sect2>
</sect1>
<sect1 id="handlingplcondit"><title>Handling Conditions In Virtuoso/PL Procedures</title>
<para>
Condition handlers determine the behavior of a Virtuoso/PL procedure when a
condition occurs. You can declare one or more condition handlers in your Virtuoso/PL
procedure for general SQL conditions or specific SQLSTATE values.
</para>
<para>
If a statement in your procedure raises an SQLEXCEPTION condition and you declared
a handler for the specific SQLSTATE or SQLEXCEPTION condition the server passes control
to that handler.
</para>
<para>
If a statement in your Virtuoso/PL procedure raises an SQLEXCEPTION condition,
and you have not declared a handler for the specific SQLSTATE or the
SQLEXCEPTION condition, the server passes the exception to the calling procedure (if any).
If the procedure call is at the top-level, then the exception is signalled to the calling
client.
</para>
<para>
Handlers are active only for the duration of the enclosing compound statement. When an exception
is thrown outside the handler's scope then this handler is never called.
</para>
<sect2 id="declarecondit"><title>Declaring Condition Handlers</title>
<para>
The general form of handler declaration is:
</para>
<programlisting>
DECLARE <handler_type> HANDLER FOR
<condition> [, <condition [...]]
<sql_procedure_statement>;
</programlisting>
<para>
For compatibility handlers can be declared also as :
</para>
<programlisting>
WHENEVER <condition> [GOTO <label>|DEFAULT];
</programlisting>
<para>
When Virtuoso raises a condition that matches the <condition>, the <sql_procedure_statement>
gets executed and when (and if) it finishes the execution continues according to the <handler_type>.
</para>
<programlisting>
<handler_type>
</programlisting>
<para>
<emphasis>CONTINUE</emphasis> - Specifies that after <sql_procedure_statement> completes,
execution continues with the statement after the statement which caused the error.
</para>
<para>
<emphasis>EXIT</emphasis> - Specifies that after <sql_procedure_statement> completes,
execution continues after the end of the compound statement that contains the declaration of the handler.
</para>
<programlisting>
<condition>
</programlisting>
<para>
<emphasis>NOT FOUND</emphasis> - Identifies any condition that results in SQL STATE = SQL_NO_DATA_FOUND (+100)
</para>
<para>
<emphasis>SQLEXCEPTION</emphasis> - Identifies all character SQL STATEs excluding ones starting with '01', '02' and '00'
</para>
<para>
<emphasis>SQLWARNING</emphasis> - Identifies character SQL STATEs starting with '01'.
This is a shortcut for SQLSTATE '01*'
</para>
<para>
<emphasis>SQLSTATE [VALUE] '<sql_state_mask>'</emphasis> - Identifies character SQL STATEs.
The <sql_state_mask> can be a full 5 character value or 0-4 characters followed by an '*'.
When a '*' is present then any SQL STATE signal led which starts with the same characters as
the <sql_state_mask> before the '*' will cause that handler to execute.
For example the exception with SQL state '42S22' will match all the following
</para>
<programlisting>
SQLSTATE <conditions> :
SQLSTATE '*' SQLSTATE '42*'
SQLSTATE '42S22'
</programlisting>
<note><title>Note:</title>
<para>Handler's call priority is determined by the number of matching characters in this mask.</para>
</note>
<programlisting>
<sql_procedure_statement>
</programlisting>
<para>
This can be any allowed Virtuoso/PL statement as well as an compound statement. This statement is
executed in the same procedure context as the procedure body itself, so any labels and variables in the
procedure body can be used and RETURN causes the procedure to end. No handler is active while the
<sql_procedure_statement> is executed. So any exception raised is passed directly to the procedure
caller. The <sql_procedure_statement> can be empty resulting in the <handler_type> action being
taken right after setting the __SQL_STATE & __SQL_MESSAGE variables.
</para>
<para>
When multiple active handlers <condition>s match the exception being raised Virtuoso chooses the closest to the
statement raised the exception that has a largest call priority.
This means that if an exception is there are two handlers with condition SQLSTATE '4*' and SQLSTATE '42*'
and an exception with SQL STATE '42S22' is raised the handler with <condition> '42*' will be called.
</para>
<programlisting>
WHENEVER <condition> GOTO <label>
</programlisting>
<para>is an equivalent of:</para>
<programlisting>
DECLARE EXIT HANDLER FOR <condition> GOTO label.
</programlisting>
<programlisting>
WHENEVER <condition> DEFAULT
</programlisting>
<para>is equivalent of:</para>
<programlisting>
DECLARE EXIT HANDLER FOR <condition> RESIGNAL;
</programlisting>
<para>
The following examples demonstrate simple common handlers:
</para>
<para>
<emphasis>CONTINUE handler:</emphasis> The handler assigns a value of 1 to a local variable at_end when a NOT FOUND condition
is raised. The execution then continues with the statement after the signal.
</para>
<programlisting>
create procedure test1 ()
{
declare at_end integer;
at_end := 0;
declare continue handler for NOT FOUND at_end := 1;
result_names (at_end);
result (at_end);
signal (100);
result (at_end);
}
</programlisting>
<para>
When this procedure gets executed it returns the following result set :
</para>
<screen>
0
1
</screen>
<para>
<emphasis>EXIT handler:</emphasis> The handler assigns a value of 2 to a local variable at_end when a NOT_FOUND condition is raised.
The execution then continues with the statement after the compound statement containing the signal.
</para>
<programlisting>
create procedure test2 ()
{
declare at_end integer;
result_names (at_end);
at_end := 0;
declare exit handler for NOT FOUND at_end := 1;
{
result (at_end);
signal (100);
result (3);
}
result (at_end);
}
</programlisting>
<para>
When this procedure gets executed it returns the following result set :
</para>
<screen>
0
1
</screen>
<programlisting>
__SQL_STATE and __SQL_MESSAGE variables.
</programlisting>
<para>
All Virtuoso/PL procedure have two variables implicitly declared :
</para>
<programlisting>
declare __SQL_STATE any;
declare __SQL_MESSAGE varchar;
</programlisting>
<para>
Initially they are set to 0.
</para>
<para>
When an exception is raised these variables are set as follows :
</para>
<programlisting>
__SQL_STATE gets the SQL STATE (character string or integer 100 for NOT FOUND)
__SQL_MESSAGE gets the SQL MESSAGE (character) or NULL if no message.
</programlisting>
<para>
Their values are preserved until the next exception overwrites them.
</para>
<programlisting>
RESIGNAL statement
Syntax :
RESIGNAL [ '<new_sql_state>' ]
RESIGNAL is a shortcut for signal (__SQL_STATE, __SQL_MESSAGE)
RESIGNAL '<new_sql_state>' is a shortcut for signal ('<new_sql_state>', __SQL_MESSAGE);
</programlisting>
<para>
This statement resignals the current exception to the caller of the procedure.
</para>
</sect2>
<sect2 id="stacktracesql"><title>Stack Trace Reporting On Sql Error Generation</title>
<para>
When an exception occurs the Virtuoso server has the ability to provide information
about the procedure call stack. It appends the call stack information to the
error message text. There are also line numbers besides each level of the call stack
which are a Virtuoso/PL procedure. The line numbers mark the beginning of the
innermost compound statement.
</para>
<para>
The call stack reporting mode is controlled by the "CallstackOnException" option
in the <link linkend="parameters"><emphasis>Parameters</emphasis> section of the Virtuoso INI file</link>.
</para>
<para>
This parameter takes the following values:
</para>
<simplelist>
<member>0 (default) - Call stack reporting disabled.</member>
<member>1 - Call stack is reported but does not include values of arguments.</member>
<member>2 - Call stack is reported and contains all available information.</member>
</simplelist>
<para>
Call stack reporting can be a security hole because it can demonstrate internal logic of the system to the end user;
this is especially important for dynamic web pages.
Mode 2 is especially insecure because it may print values of function arguments that may contain confidential information.
</para>
<para>
Some client applications are unable to handle long error messages properly.
Client-side APIs for ODBC and similar protocols assume that client should
allocate a buffer for error message string and then ask the API to save
the message string to the specified buffer of the specified size. Not all
client applications work properly if a message does not fit in the buffer.
If an client application you use reports an empty string instead of error
message or displays a message like 'Error message is too long' then you may
wish to decrease the value of the "CallstackOnException" option to keep
messages shorter.
</para>
</sect2></sect1>
&pldebugger;
&rowlevelsecurity;
<sect1 id="vexqrparl"><title>Vectored Execution and Query Parallelization</title>
<para>Note: This feature only applies to Virtuoso 7.0 and later.</para>
<para>Vectored execution means executing queries or stored procedures simultaneously on
multiple sets of parameters. Further, when a query contains a <emphasis>JOIN</emphasis>, a
single invocation of the query will, with vectored execution, execute every consecutive
<emphasis>JOIN</emphasis> step with multiple inputs. When every stage of a query's evaluation
is performed on a large number of intermediate result rows at a time, two benefits are
obtained:</para>
<orderedlist>
<listitem>The interpretation overhead disappears and</listitem>
<listitem>Locality of reference in <emphasis>JOINs</emphasis> can be better exploited.</listitem>
</orderedlist>
<para>For example, with a <emphasis>JOIN</emphasis> like:</para>
<programlisting><![CDATA[
SELECT COUNT(*)
FROM part,
lineitem
WHERE l_partkey = p_partkey
AND p_size < 23
OPTION ( LOOP, ORDER );
]]></programlisting>
<para>the outermost <emphasis>LOOP</emphasis> of the query will look for parts with
<emphasis>p_size < 23</emphasis>. The part keys of these are used as lookup keys for an index on
<emphasis>l_partkey</emphasis> in <emphasis>lineitem</emphasis>. This index translates these
values into values of the primary key, <emphasis>l_orderkey</emphasis>,
<emphasis>l_linenumber</emphasis>, which is then used to get the data row with
the price and discount. When each of these steps is done with tens of thousands of
values at the same time, the SQL interpretation overhead is almost completely
eliminated and locality can be exploited when accessing nearby rows. The chance of
hitting nearby rows also increases when the size of the intermediate result batch
increases.</para>
<sect2 id="vexqrparlautoqp"><title>Automatic Query Parallelization</title>
<para>If a query does not modify data, executes in <emphasis>READ COMMITTED</emphasis>
isolation, and contains some form of aggregation or <emphasis>ORDER BY</emphasis>, it can
be automatically parallelized. Parallelization typically splits the query's outermost
<emphasis>LOOP</emphasis> into approximately equal size chunks, which are independently
evaluated each on its own thread. The results are merged together when all are ready,
and are combined in an aggregation or <emphasis>ORDER BY</emphasis>. This is entirely
transparent to the user.</para>
</sect2>
<sect2 id="vexqrparlconfp"><title>Configuration Parameters for Vectoring and Parallelization</title>
<para>The following virtuoso.ini [Parameters] section entries concern query parallelization and vectoring:</para>
<para>Note: These settings only apply to Virtuoso 7.0 and later.</para>
<itemizedlist mark="bullet">
<listitem><link linkend="ini_Parameters">AsyncQueueMaxThreads</link>:
<para>Sets the number of threads in a pool that is used for getting extra threads for running
queries and for aq_request. Each running statement has at least one thread that is not allocated
from this pool plus zero or more threads from this pool.</para>
<para>Setting the pool size to the number of cores plus a few is a reasonable default. On
platforms with core multithreading, one can count a core thread as a core for purposes of
this parameter.</para>
<para>If one expects to run many slow <link linkend="fn_aq_requests"><function>aq_requests()</function></link>
(see <link linkend="fn_async_queue"><function>async_queue()</function></link>,
<link linkend="fn_aq_request"><function>aq_request()</function></link>, etc.),
then the number of threads should be increased by the number of slow threads one expects.
</para>
<para>Slow threads are typically I/O bound threads used for web crawling or similar long-latency,
low-CPU activity.</para>
<para>Note: Only effective with Virtuoso 7.0 and later.</para>
</listitem>
<listitem><link linkend="ini_Parameters">ThreadsPerQuery</link>:
<para>This is maximum number of threads that can be claimed from the thread pool by a single
query. A value of one means that no query parallelization will take place, and all queries
will run single threaded.</para>
<para>The number of cores on the machine is a reasonable default if running large queries.</para>
<para>Note that since every query is served by at least one thread, a single query taking
all the extra threads will not prevent other queries from progressing.</para>
<para>Note: Only effective with Virtuoso 7.0 and later.</para>
</listitem>
<listitem><link linkend="ini_Parameters">VectorSize</link>
<para>This the number of simultaneous sets of query variable bindings processed at one time.
The default is 10,000, which is good for most cases.</para>
<para>If we are evaluating the query:</para>
<programlisting><![CDATA[
SELECT COUNT (*)
FROM t1 a,
t1 b
WHERE a.row_no + 1 = b.row_no
OPTION (LOOP, ORDER)
]]></programlisting>
<para>with vector size of 10,000, then 10,000 rows of t1 a will be fetched first;
1 will be added to the 10,000 row_no values;
and then the corresponding row of t1 b will be fetched for the 10,000 row_no of t1 a.
This process will repeat until enough batches of t1 a have been fetched to come to its end.</para>
<para>Note: Only effective with Virtuoso 7.0 and later.</para>
</listitem>
<listitem><link linkend="ini_Parameters">AdjustVectorSize</link>:
<para>Using a larger vector size when evaluating large queries with indexed random-access can
yield up to a 3x speed-up relative to using the default vector size. However, always using a
large vector size will prohibitively increase the overhead of running small queries. For this
reason, there is the option to adaptively select the vector size. Set AdjustVectorSize = 1
to enable this feature. The SQL execution engine will increase the vector size when it sees
an index lookup that does not get good locality, (e.g., after sorting the keys to look for,
too few consecutive lookups fall on the same page). Having more keys to look up increases
the chance that consecutive keys should be found on the same page, thus eliminating much of
the index lookup cost.</para>
<para>Note: Only effective with Virtuoso 7.0 and later.</para>
</listitem>
<listitem><link linkend="ini_Parameters">MaxVectorSize</link>:
<para>When AdjustVectorSize is on, this setting gives the maximum vector size. The
default is 1,000,000 and the largest allowed value is about 3,500,000.</para>
<para>Note: Only effective with Virtuoso 7.0 and later.</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
</chapter>
|