提交 d69d5052 authored 作者: Sergey Kalashnikov's avatar Sergey Kalashnikov 提交者: Sergi Vladykin

Custom data types handler (#462)

* Custom data types handler

* Updated doc, changelog, dictionary. Fixed review comment.

* Extended the custom types handler interface

* Fixed test

* Fixed review comments

* Added note on ABI stability of the feature to docs
上级 237dda41
......@@ -85,6 +85,8 @@ Advanced
Database Upgrade</a><br />
<a href="#java_objects_serialization">
Java Objects Serialization</a><br />
<a href="#custom_data_types_handler_api">
Custom Data Types Handler API</a><br />
<a href="#limits_limitations">
Limits and Limitations</a><br />
<a href="#glossary_links">
......@@ -1691,6 +1693,29 @@ Please note that this SQL statement can only be executed before any tables are d
</ul>
</p>
<h2 id="custom_data_types_handler_api">Custom Data Types Handler API</h2>
<p>
It is possible to extend the type system of the database by providing your own implementation
of minimal required API basically consisting of type identification and conversion routines.
</p>
<p>
In order to enable this feature, set the system property <code>h2.customDataTypesHandler</code> (default: null) to the fully qualified name of the class providing <a href="../javadoc/org/h2/api/CustomDataTypesHandler.html">CustomDataTypesHandler</a> interface implementation. <br>
The instance of that class will be created by H2 and used to:
<ul>
<li>resolve the names and identifiers of extrinsic data types.
</li>
<li>convert values of extrinsic data types to and from values of built-in types.
</li>
<li>provide order of the data types.
</li>
</ul>
</p>
<p>This is a system-level setting, i.e. affects all the databases.</p>
<p><b>Note: </b>Please keep in mind that this feature may not possibly provide the same ABI stability level as other features as it exposes many of the H2 internals. You may be required to update your code occasionally due to internal changes in H2 if you are going to use this feature.
</p>
<h2 id="limits_limitations">Limits and Limitations</h2>
<p>
This database has the following known limitations:
......
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Added API for handling custom data types (System property "h2.customDataTypesHandler", API org.h2.api.CustomDataTypesHandler).
</li>
<li>Added support for invisible columns.
</li>
</ul>
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
import org.h2.store.DataHandler;
import org.h2.value.DataType;
import org.h2.value.Value;
/**
* Custom data type handler
* Provides means to plug-in custom data types support
*
* Please keep in mind that this feature may not possibly
* provide the same ABI stability level as other features
* as it exposes many of the H2 internals. You may be
* required to update your code occasionally due to internal
* changes in H2 if you are going to use this feature
*/
public interface CustomDataTypesHandler {
/**
* Get custom data type given its name
*
* @param name data type name
* @return custom data type
*/
DataType getDataTypeByName(String name);
/**
* Get custom data type given its integer id
*
* @param type identifier of a data type
* @return custom data type
*/
DataType getDataTypeById(int type);
/**
* Get order for custom data type given its integer id
*
* @param type identifier of a data type
* @return order associated with custom data type
*/
int getDataTypeOrder(int type);
/**
* Convert the provided source value into value of given target data type
* Shall implement conversions to and from custom data types.
*
* @param source source value
* @param targetType identifier of target data type
* @return converted value
*/
Value convert(Value source, int targetType);
/**
* Get custom data type class name given its integer id
*
* @param type identifier of a data type
* @return class name
*/
String getDataTypeClassName(int type);
/**
* Get custom data type identifier given corresponding Java class
* @param cls Java class object
* @return type identifier
*/
int getTypeIdFromClass(Class<?> cls);
/**
* Get {@link org.h2.value.Value} object
* corresponding to given data type identifier and data.
*
* @param type custom data type identifier
* @param data underlying data type value
* @param dataHandler data handler object
* @return Value object
*/
Value getValue(int type, Object data, DataHandler dataHandler);
/**
* Converts {@link org.h2.value.Value} object
* to the specified class.
*
* @param value
* @param cls
* @return result
*/
Object getObject(Value value, Class<?> cls);
/**
* Checks if type supports add operation
*
* @param type custom data type identifier
* @return True, if custom data type supports add operation
*/
boolean supportsAdd(int type);
/**
* Get compatible type identifier that would not overflow
* after many add operations.
*
* @param type identifier of a type
* @return resulting type identifier
*/
int getAddProofType(int type);
}
......@@ -522,6 +522,16 @@ public class SysProperties {
public static final String JAVA_OBJECT_SERIALIZER =
Utils.getProperty("h2.javaObjectSerializer", null);
/**
* System property <code>h2.customDataTypesHandler</code>
* (default: null).<br />
* The CustomDataTypesHandler class name that is used
* to provide support for user defined custom data types.
* It must be the same on client and server to work correctly.
*/
public static final String CUSTOM_DATA_TYPES_HANDLER =
Utils.getProperty("h2.customDataTypesHandler", null);
private static final String H2_BASE_DIR = "h2.baseDir";
private SysProperties() {
......
......@@ -22,6 +22,7 @@ import org.h2.mvstore.type.DataType;
import org.h2.result.SortOrder;
import org.h2.store.DataHandler;
import org.h2.tools.SimpleResultSet;
import org.h2.util.JdbcUtils;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueArray;
......@@ -67,6 +68,7 @@ public class ValueDataType implements DataType {
private static final int STRING_0_31 = 68;
private static final int BYTES_0_31 = 100;
private static final int SPATIAL_KEY_2D = 132;
private static final int CUSTOM_DATA_TYPE = 133;
final DataHandler handler;
final CompareMode compareMode;
......@@ -428,6 +430,14 @@ public class ValueDataType implements DataType {
break;
}
default:
if (JdbcUtils.customDataTypesHandler != null) {
byte[] b = v.getBytesNoCopy();
buff.put((byte)CUSTOM_DATA_TYPE).
putVarInt(type).
putVarInt(b.length).
put(b);
break;
}
DbException.throwInternalError("type=" + v.getType());
}
}
......@@ -592,6 +602,16 @@ public class ValueDataType implements DataType {
}
case SPATIAL_KEY_2D:
return getSpatialDataType().read(buff);
case CUSTOM_DATA_TYPE: {
if (JdbcUtils.customDataTypesHandler != null) {
int customType = readVarInt(buff);
int len = readVarInt(buff);
byte[] b = DataUtils.newBytes(len);
buff.get(b, 0, len);
return JdbcUtils.customDataTypesHandler.convert(ValueBytes.getNoCopy(b), customType);
}
throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "No CustomDataTypesHandler has been set up");
}
default:
if (type >= INT_0_15 && type < INT_0_15 + 16) {
return ValueInt.get(type - INT_0_15);
......
......@@ -21,6 +21,7 @@ import java.util.HashSet;
import java.util.Properties;
import javax.naming.Context;
import javax.sql.DataSource;
import org.h2.api.CustomDataTypesHandler;
import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer;
import org.h2.engine.SysProperties;
......@@ -38,6 +39,11 @@ public class JdbcUtils {
*/
public static JavaObjectSerializer serializer;
/**
* Custom data types handler to use.
*/
public static CustomDataTypesHandler customDataTypesHandler;
private static final String[] DRIVERS = {
"h2:", "org.h2.Driver",
"Cache:", "com.intersys.jdbc.CacheDriver",
......@@ -117,6 +123,15 @@ public class JdbcUtils {
throw DbException.convert(e);
}
}
String customTypeHandlerClass = SysProperties.CUSTOM_DATA_TYPES_HANDLER;
if (customTypeHandlerClass != null) {
try {
customDataTypesHandler = (CustomDataTypesHandler) loadUserClass(customTypeHandlerClass).newInstance();
} catch (Exception e) {
throw DbException.convert(e);
}
}
}
/**
......
......@@ -682,6 +682,11 @@ public class DataType {
return ValueGeometry.getFromGeometry(x);
}
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getValue(type,
rs.getObject(columnIndex),
session.getDataHandler());
}
throw DbException.throwInternalError("type="+type);
}
return v;
......@@ -764,6 +769,9 @@ public class DataType {
case Value.GEOMETRY:
return GEOMETRY_CLASS_NAME;
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getDataTypeClassName(type);
}
throw DbException.throwInternalError("type="+type);
}
}
......@@ -779,6 +787,9 @@ public class DataType {
throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?");
}
DataType dt = TYPES_BY_VALUE_TYPE.get(type);
if (dt == null && JdbcUtils.customDataTypesHandler != null) {
dt = JdbcUtils.customDataTypesHandler.getDataTypeById(type);
}
if (dt == null) {
dt = TYPES_BY_VALUE_TYPE.get(Value.NULL);
}
......@@ -971,6 +982,9 @@ public class DataType {
} else if (LocalDateTimeUtils.isOffsetDateTime(x)) {
return Value.TIMESTAMP_TZ;
} else {
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getTypeIdFromClass(x);
}
return Value.JAVA_OBJECT;
}
}
......@@ -1094,6 +1108,9 @@ public class DataType {
} else if (LocalDateTimeUtils.isOffsetDateTime(x.getClass())) {
return LocalDateTimeUtils.offsetDateTimeToValue(x);
} else {
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getValue(type, x, session.getDataHandler());
}
return ValueJavaObject.getNoCopy(x, null, session.getDataHandler());
}
}
......@@ -1132,7 +1149,11 @@ public class DataType {
* @return the data type object
*/
public static DataType getTypeByName(String s) {
return TYPES_BY_NAME.get(s);
DataType result = TYPES_BY_NAME.get(s);
if (result == null && JdbcUtils.customDataTypesHandler != null) {
result = JdbcUtils.customDataTypesHandler.getDataTypeByName(s);
}
return result;
}
/**
......@@ -1178,7 +1199,29 @@ public class DataType {
case Value.LONG:
case Value.SHORT:
return true;
case Value.BOOLEAN:
case Value.TIME:
case Value.DATE:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
case Value.BYTES:
case Value.UUID:
case Value.STRING:
case Value.STRING_IGNORECASE:
case Value.STRING_FIXED:
case Value.BLOB:
case Value.CLOB:
case Value.NULL:
case Value.JAVA_OBJECT:
case Value.UNKNOWN:
case Value.ARRAY:
case Value.RESULT_SET:
case Value.GEOMETRY:
return false;
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.supportsAdd(type);
}
return false;
}
}
......@@ -1202,7 +1245,31 @@ public class DataType {
return Value.DECIMAL;
case Value.SHORT:
return Value.LONG;
case Value.BOOLEAN:
case Value.DECIMAL:
case Value.TIME:
case Value.DATE:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
case Value.BYTES:
case Value.UUID:
case Value.STRING:
case Value.STRING_IGNORECASE:
case Value.STRING_FIXED:
case Value.BLOB:
case Value.CLOB:
case Value.DOUBLE:
case Value.NULL:
case Value.JAVA_OBJECT:
case Value.UNKNOWN:
case Value.ARRAY:
case Value.RESULT_SET:
case Value.GEOMETRY:
return type;
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getAddProofType(type);
}
return type;
}
}
......@@ -1253,12 +1320,44 @@ public class DataType {
} else if (paramClass == Array.class) {
return new JdbcArray(conn, v, 0);
}
if (v.getType() == Value.JAVA_OBJECT) {
switch (v.getType()) {
case Value.JAVA_OBJECT: {
Object o = SysProperties.serializeJavaObject ? JdbcUtils.deserialize(v.getBytes(),
conn.getSession().getDataHandler()) : v.getObject();
if (paramClass.isAssignableFrom(o.getClass())) {
return o;
}
break;
}
case Value.BOOLEAN:
case Value.BYTE:
case Value.SHORT:
case Value.INT:
case Value.LONG:
case Value.DECIMAL:
case Value.TIME:
case Value.DATE:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
case Value.BYTES:
case Value.UUID:
case Value.STRING:
case Value.STRING_IGNORECASE:
case Value.STRING_FIXED:
case Value.BLOB:
case Value.CLOB:
case Value.DOUBLE:
case Value.FLOAT:
case Value.NULL:
case Value.UNKNOWN:
case Value.ARRAY:
case Value.RESULT_SET:
case Value.GEOMETRY:
break;
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getObject(v, paramClass);
}
}
throw DbException.getUnsupportedException("converting to class " + paramClass.getName());
}
......
......@@ -530,6 +530,10 @@ public class Transfer {
}
break;
default:
if (JdbcUtils.customDataTypesHandler != null) {
writeBytes(v.getBytesNoCopy());
break;
}
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type);
}
}
......@@ -706,6 +710,9 @@ public class Transfer {
}
return ValueGeometry.get(readString());
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.convert(ValueBytes.getNoCopy(readBytes()), type);
}
throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type);
}
}
......
......@@ -274,56 +274,59 @@ public abstract class Value {
static int getOrder(int type) {
switch (type) {
case UNKNOWN:
return 1;
return 1_000;
case NULL:
return 2;
return 2_000;
case STRING:
return 10;
return 10_000;
case CLOB:
return 11;
return 11_000;
case STRING_FIXED:
return 12;
return 12_000;
case STRING_IGNORECASE:
return 13;
return 13_000;
case BOOLEAN:
return 20;
return 20_000;
case BYTE:
return 21;
return 21_000;
case SHORT:
return 22;
return 22_000;
case INT:
return 23;
return 23_000;
case LONG:
return 24;
return 24_000;
case DECIMAL:
return 25;
return 25_000;
case FLOAT:
return 26;
return 26_000;
case DOUBLE:
return 27;
return 27_000;
case TIME:
return 30;
return 30_000;
case DATE:
return 31;
return 31_000;
case TIMESTAMP:
return 32;
return 32_000;
case TIMESTAMP_TZ:
return 34;
return 34_000;
case BYTES:
return 40;
return 40_000;
case BLOB:
return 41;
return 41_000;
case JAVA_OBJECT:
return 42;
return 42_000;
case UUID:
return 43;
return 43_000;
case GEOMETRY:
return 44;
return 44_000;
case ARRAY:
return 50;
return 50_000;
case RESULT_SET:
return 51;
return 51_000;
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getDataTypeOrder(type);
}
throw DbException.throwInternalError("type:"+type);
}
}
......@@ -967,6 +970,9 @@ public abstract class Value {
case GEOMETRY:
return ValueGeometry.get(s);
default:
if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.convert(this, targetType);
}
throw DbException.throwInternalError("type=" + targetType);
}
} catch (NumberFormatException e) {
......
......@@ -91,6 +91,7 @@ import org.h2.test.jdbc.TestCallableStatement;
import org.h2.test.jdbc.TestCancel;
import org.h2.test.jdbc.TestConcurrentConnectionUsage;
import org.h2.test.jdbc.TestConnection;
import org.h2.test.jdbc.TestCustomDataTypesHandler;
import org.h2.test.jdbc.TestDatabaseEventListener;
import org.h2.test.jdbc.TestDriver;
import org.h2.test.jdbc.TestJavaObject;
......@@ -772,6 +773,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestTransactionIsolation());
addTest(new TestUpdatableResultSet());
addTest(new TestZloty());
addTest(new TestCustomDataTypesHandler());
// jdbcx
addTest(new TestConnectionPool());
......
aacute aaload aastore abalance abba abbreviate abbreviated abbreviates
abbreviation ability able abnormal abnormally abort aborted about above abs
abbreviation abi ability able abnormal abnormally abort aborted about above abs
absence absent absolute absolutely abstract abstraction abstractions abstracts
aca accept acceptable acceptance accepted accepting accepts access accessadmin
accessed accesses accessible accessing accesskey accessor accidentally
......@@ -97,7 +97,7 @@ classifications classloader classloaders classname classpath clause clauses claz
clean cleaned cleaner cleaning cleanup clear cleared clearer clearing clearly
clears cleartext click clicked clicking client clients clinton clip clipboard
clob clobs clock clocks clone cloneable cloned closable close closeable closed
closely closer closes closest closing cloud clubs clue clunky cluster clustered
closely closer closes closest closing cloud cls clubs clue clunky cluster clustered
clustering cmd cms cnf cnrs cnt coalesce code codebase codebook coded codegen
codehaus codes coding codist coffee cognitect col cold coldrick coll
collaborative collapse collate collateral collation collations collator collators
......@@ -234,12 +234,12 @@ exfsys exhausted exhibit exist existed existence existing exists exit exited
exits exp expand expanded expands expansion expect expected expecting expedites
expense expenses expensive experience experimental experiments experts expiration
expired expires explain explanation explicit explicitconstructorcall explicitly
exploit explorer exponent exponential export exported exports expose exposed expr
express expressed expression expressions expressly exps ext extend extendable
exploit explorer exponent exponential export exported exports expose exposed exposes
expr express expressed expression expressions expressly exps ext extend extendable
extended extending extends extensible extension extensions extensively extent
extern external externally extra extract extracted extracting extracts extras
extreme extremely extremes eye fabien fabric facade face facilitate facility fact
factor factorial factories factory factual fadd fail failed failing fails failure
extreme extremely extremes extrinsic eye fabien fabric facade face facilitate facility
fact factor factorial factories factory factual fadd fail failed failing fails failure
failures fair fake fall fallback falls faload false familiar families family faq
far fashion fashioned fast faster fastest fastore fat fatal faulhaber fault
favicon favorite fbj fcmpg fcmpl fconst fdiv feature features feb februar
......@@ -252,7 +252,7 @@ firebird firebirdsql fired firefox firewall first firstname fish fit fitness fit
fitting five fix fixed fixes fixing fkcolumn fktable flag flags flash flashback
flat fle fletcher flexibility flexible flexive flip flipped fload float floating
flooding floor florent flow flower flows fluent fluid flush flushed flushes
flushing flux fly flyway fmb fmc fml fmrn fmul fmxx fmxxx fneg focus focusable
flushing flux fly flyway fmb fmc fml fmrn fmt fmul fmxx fmxxx fneg focus focusable
fog fogh folder follow followed following follows font fontes foo footer footers
footprint for forall forbidden force forced forcefully forces forcing foreign
forever forge forget forgetting forgot forgotten fork form formal format
......@@ -297,12 +297,12 @@ however hprof href hsql hsqldb htime htm html http httpdocs https huang hub huff
huffman huge human hundred hundreds hurt hyc hyde hyperbolic hyperlink hypersonic
hyt iacute iadd iaload iand iastore ibm iced iceland iciql icirc icmpeq icmpge
icmpgt icmple icmplt icmpne ico icon iconified icons iconst icu ide idea ideal
ideas identical identified identifier identifiers identify identifying identities
identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq ifexists
ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored
ideas identical identification identified identifier identifiers identify identifying
identities identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq
ifexists ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored
ignoredriverprivileges ignorelist ignores ignoring igrave iinc ikura ikvm ikvmc
illegal iload image imageio images img iml immediately immutable imola imp impact
imperial impersonate impl imple implement implementation implementations
illegal iload image imageio images imaginary img iml immediately immutable imola imp
impact imperial impersonate impl imple implement implementation implementations
implemented implementing implements implication implicit implicitly implied
implies import important imported importing imports impose imposes impossible
improperly improve improved improvement improvements improves improving imul
......@@ -329,11 +329,11 @@ instead institutes instr instruction instructions instrument instrumentation
instrumented int intact integer integers integrate integrated integration
integrity intellectual intelli intended intentional inter interaction interactive
intercepted interest interested interesting interface interfaces interleave
interleaved interleaving intermediate intern internal internally international
internationalization internet interpolation interpret interpreted interpreter
interpreting interprets interrupt interrupted interrupting interruption intersect
intersecting intersection intersects intersys interval intervals into intra
introduce introduced introduction inttypes inv inval invalid invalidate
interleaved interleaving intermediate intern internal internally internals
international internationalization internet interpolation interpret interpreted
interpreter interpreting interprets interrupt interrupted interrupting interruption
intersect intersecting intersection intersects intersys interval intervals into
intra introduce introduced introduction inttypes inv inval invalid invalidate
invalidated invectorate invented invention inventor inversed invert inverting
invisible invocation invoice invoiceid invoke invokeinterface invoker
invokespecial invokestatic invokevirtual involve involved involves ior iota ipt
......@@ -363,8 +363,8 @@ lawsuits lax layer layers layout lazily lazy lcase lceil lck lcmp lconst ldap
ldbc ldc ldiv ldquo lea leach lead leading leads leaf leak leaked leaks leaning
leap learning least leave leaves leaving lee left leftmost leftover legacy legal
legend lehmann lempel len length lengths lenient leod less lesser let lets letter
letters level levels lfloor lgpl liability liable lib liberal libraries library
licensable license licensed licensees licenses licensing lies life lifespan
letters level levels lexicographical lfloor lgpl liability liable lib liberal libraries
library licensable license licensed licensees licenses licensing lies life lifespan
lifetime liftweb light lightweight like likely lim limit limitation limitations
limited limiting limits line linear linearly linefeed lines linestring lineup
link linkage linked links linq lint linux liq liqui lir lirs lisboa list listed
......@@ -431,9 +431,9 @@ november now nowait nowrap npl nsi nsis nsub ntext ntfs nth ntilde nucleus nul
null nullable nullid nullif nulls nullsoft num number numbering numbers numeral
numerals numeric numerical nuxeo nvarchar nvl oacute obey obj object objects
obligation obligations observer obsolete obtain obtained obtains obviously
occupied occupies occupy occur occurred occurrence occurrences occurs ocirc octal
octet october octype odbc odbcad odd odg off offending offer offered offering
offers office official offline offset offsets often ogc ograve ohloh oid okay
occasionally occupied occupies occupy occur occurred occurrence occurrences occurs
ocirc octal octet october octype odbc odbcad odd odg off offending offer offered
offering offers office official offline offset offsets often ogc ograve ohloh oid okay
okra olap olapsys old older oldest oline oliver olivier omega omicron omissions
omitted omitting once onchange onclick one ones onfocus ongoing onkeydown onkeyup
online onload only onmousedown onmousemove onmouseout onmouseover onmouseup
......@@ -554,7 +554,7 @@ ridvan rife right rightmost rights rijndael ring rioyxlgt risk risks risky rlm
rmd rmdir rmerr rmi rmiregistry rnd rnfr rnto road roadmap roads robert roc rogue
rojas role roles roll rollback rollbacks rolled rolling rollover rolls roman room
root roots rot rotate round rounded rounding roundmagic rounds routine routinely
row rowcount rowid rowlock rownum rows rowscn rowsize roy royalty rpad rpm rsa
routines row rowcount rowid rowlock rownum rows rowscn rowsize roy royalty rpad rpm rsa
rsaquo rsquo rss rtree rtrim ruby ruebezahl rule rules run rund rundll runnable
runner runners running runs runscript runtime rwd rws sabine safari safe safely
safes safety said sainsbury salary sale sales saload salt salz sam same
......@@ -602,8 +602,8 @@ specification specified specifies specify specifying specs speed speeds speedup
spell spellcheck spellchecker spelled spelling spends spent sphere spi spiced
spin spliced split splits splitting sponsored spot spots spr spread spring
springframework springfuse sql sqlexpress sqli sqlite sqlj sqlnulls sqlserver
sqlstate sqlxml sqrt square squill squirrel src srcs srid ssd ssl stabilize
stable stack stackable stacked stage stages stamp standalone standard
sqlstate sqlxml sqrt square squill squirrel src srcs srid ssd ssl stability
stabilize stable stack stackable stacked stage stages stamp standalone standard
standardized standards standby standing stands star staring start started starter
starting starts startup starvation starves stat state stated statement statements
states static stating station statistic statistical statisticlog statistics stats
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论