View Javadoc

1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeMap;
32  
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
35  import org.apache.hadoop.hbase.io.hfile.Compression;
36  import org.apache.hadoop.hbase.regionserver.StoreFile;
37  import org.apache.hadoop.hbase.util.Bytes;
38  import org.apache.hadoop.io.WritableComparable;
39  
40  /**
41   * HTableDescriptor contains the name of an HTable, and its
42   * column families.
43   */
44  public class HTableDescriptor implements WritableComparable<HTableDescriptor> {
45  
46    // Changes prior to version 3 were not recorded here.
47    // Version 3 adds metadata as a map where keys and values are byte[].
48    // Version 4 adds indexes
49    // Version 5 removed transactional pollution -- e.g. indexes
50    public static final byte TABLE_DESCRIPTOR_VERSION = 5;
51  
52    private byte [] name = HConstants.EMPTY_BYTE_ARRAY;
53    private String nameAsString = "";
54  
55    // Table metadata
56    protected Map<ImmutableBytesWritable, ImmutableBytesWritable> values =
57      new HashMap<ImmutableBytesWritable, ImmutableBytesWritable>();
58  
59    public static final String FAMILIES = "FAMILIES";
60    public static final ImmutableBytesWritable FAMILIES_KEY =
61      new ImmutableBytesWritable(Bytes.toBytes(FAMILIES));
62    public static final String MAX_FILESIZE = "MAX_FILESIZE";
63    public static final ImmutableBytesWritable MAX_FILESIZE_KEY =
64      new ImmutableBytesWritable(Bytes.toBytes(MAX_FILESIZE));
65    public static final String READONLY = "READONLY";
66    public static final ImmutableBytesWritable READONLY_KEY =
67      new ImmutableBytesWritable(Bytes.toBytes(READONLY));
68    public static final String MEMSTORE_FLUSHSIZE = "MEMSTORE_FLUSHSIZE";
69    public static final ImmutableBytesWritable MEMSTORE_FLUSHSIZE_KEY =
70      new ImmutableBytesWritable(Bytes.toBytes(MEMSTORE_FLUSHSIZE));
71    public static final String IS_ROOT = "IS_ROOT";
72    public static final ImmutableBytesWritable IS_ROOT_KEY =
73      new ImmutableBytesWritable(Bytes.toBytes(IS_ROOT));
74    public static final String IS_META = "IS_META";
75  
76    public static final ImmutableBytesWritable IS_META_KEY =
77      new ImmutableBytesWritable(Bytes.toBytes(IS_META));
78  
79    public static final String DEFERRED_LOG_FLUSH = "DEFERRED_LOG_FLUSH";
80    public static final ImmutableBytesWritable DEFERRED_LOG_FLUSH_KEY =
81      new ImmutableBytesWritable(Bytes.toBytes(DEFERRED_LOG_FLUSH));
82  
83  
84    // The below are ugly but better than creating them each time till we
85    // replace booleans being saved as Strings with plain booleans.  Need a
86    // migration script to do this.  TODO.
87    private static final ImmutableBytesWritable FALSE =
88      new ImmutableBytesWritable(Bytes.toBytes(Boolean.FALSE.toString()));
89    private static final ImmutableBytesWritable TRUE =
90      new ImmutableBytesWritable(Bytes.toBytes(Boolean.TRUE.toString()));
91  
92    public static final boolean DEFAULT_READONLY = false;
93  
94    public static final long DEFAULT_MEMSTORE_FLUSH_SIZE = 1024*1024*64L;
95  
96    public static final long DEFAULT_MAX_FILESIZE = 1024*1024*256L;
97  
98    public static final boolean DEFAULT_DEFERRED_LOG_FLUSH = true;
99  
100   private volatile Boolean meta = null;
101   private volatile Boolean root = null;
102   private Boolean isDeferredLog = null;
103 
104   // Key is hash of the family name.
105   public final Map<byte [], HColumnDescriptor> families =
106     new TreeMap<byte [], HColumnDescriptor>(Bytes.BYTES_RAWCOMPARATOR);
107 
108   /**
109    * Private constructor used internally creating table descriptors for
110    * catalog tables: e.g. .META. and -ROOT-.
111    */
112   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families) {
113     this.name = name.clone();
114     this.nameAsString = Bytes.toString(this.name);
115     setMetaFlags(name);
116     for(HColumnDescriptor descriptor : families) {
117       this.families.put(descriptor.getName(), descriptor);
118     }
119   }
120 
121   /**
122    * Private constructor used internally creating table descriptors for
123    * catalog tables: e.g. .META. and -ROOT-.
124    */
125   protected HTableDescriptor(final byte [] name, HColumnDescriptor[] families,
126       Map<ImmutableBytesWritable,ImmutableBytesWritable> values) {
127     this.name = name.clone();
128     this.nameAsString = Bytes.toString(this.name);
129     setMetaFlags(name);
130     for(HColumnDescriptor descriptor : families) {
131       this.families.put(descriptor.getName(), descriptor);
132     }
133     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> entry:
134         values.entrySet()) {
135       this.values.put(entry.getKey(), entry.getValue());
136     }
137   }
138 
139 
140   /**
141    * Constructs an empty object.
142    * For deserializing an HTableDescriptor instance only.
143    * @see #HTableDescriptor(byte[])
144    */
145   public HTableDescriptor() {
146     super();
147   }
148 
149   /**
150    * Constructor.
151    * @param name Table name.
152    * @throws IllegalArgumentException if passed a table name
153    * that is made of other than 'word' characters, underscore or period: i.e.
154    * <code>[a-zA-Z_0-9.].
155    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
156    */
157   public HTableDescriptor(final String name) {
158     this(Bytes.toBytes(name));
159   }
160 
161   /**
162    * Constructor.
163    * @param name Table name.
164    * @throws IllegalArgumentException if passed a table name
165    * that is made of other than 'word' characters, underscore or period: i.e.
166    * <code>[a-zA-Z_0-9-.].
167    * @see <a href="HADOOP-1581">HADOOP-1581 HBASE: Un-openable tablename bug</a>
168    */
169   public HTableDescriptor(final byte [] name) {
170     super();
171     setMetaFlags(this.name);
172     this.name = this.isMetaRegion()? name: isLegalTableName(name);
173     this.nameAsString = Bytes.toString(this.name);
174   }
175 
176   /**
177    * Constructor.
178    * <p>
179    * Makes a deep copy of the supplied descriptor.
180    * Can make a modifiable descriptor from an UnmodifyableHTableDescriptor.
181    * @param desc The descriptor.
182    */
183   public HTableDescriptor(final HTableDescriptor desc) {
184     super();
185     this.name = desc.name.clone();
186     this.nameAsString = Bytes.toString(this.name);
187     setMetaFlags(this.name);
188     for (HColumnDescriptor c: desc.families.values()) {
189       this.families.put(c.getName(), new HColumnDescriptor(c));
190     }
191     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
192         desc.values.entrySet()) {
193       this.values.put(e.getKey(), e.getValue());
194     }
195   }
196 
197   /*
198    * Set meta flags on this table.
199    * Called by constructors.
200    * @param name
201    */
202   private void setMetaFlags(final byte [] name) {
203     setRootRegion(Bytes.equals(name, HConstants.ROOT_TABLE_NAME));
204     setMetaRegion(isRootRegion() ||
205       Bytes.equals(name, HConstants.META_TABLE_NAME));
206   }
207 
208   /** @return true if this is the root region */
209   public boolean isRootRegion() {
210     if (this.root == null) {
211       this.root = isSomething(IS_ROOT_KEY, false)? Boolean.TRUE: Boolean.FALSE;
212     }
213     return this.root.booleanValue();
214   }
215 
216   /** @param isRoot true if this is the root region */
217   protected void setRootRegion(boolean isRoot) {
218     // TODO: Make the value a boolean rather than String of boolean.
219     values.put(IS_ROOT_KEY, isRoot? TRUE: FALSE);
220   }
221 
222   /** @return true if this is a meta region (part of the root or meta tables) */
223   public boolean isMetaRegion() {
224     if (this.meta == null) {
225       this.meta = calculateIsMetaRegion();
226     }
227     return this.meta.booleanValue();
228   }
229 
230   private synchronized Boolean calculateIsMetaRegion() {
231     byte [] value = getValue(IS_META_KEY);
232     return (value != null)? Boolean.valueOf(Bytes.toString(value)): Boolean.FALSE;
233   }
234 
235   private boolean isSomething(final ImmutableBytesWritable key,
236       final boolean valueIfNull) {
237     byte [] value = getValue(key);
238     if (value != null) {
239       // TODO: Make value be a boolean rather than String of boolean.
240       return Boolean.valueOf(Bytes.toString(value)).booleanValue();
241     }
242     return valueIfNull;
243   }
244 
245   /**
246    * @param isMeta true if this is a meta region (part of the root or meta
247    * tables) */
248   protected void setMetaRegion(boolean isMeta) {
249     values.put(IS_META_KEY, isMeta? TRUE: FALSE);
250   }
251 
252   /** @return true if table is the meta table */
253   public boolean isMetaTable() {
254     return isMetaRegion() && !isRootRegion();
255   }
256 
257   /**
258    * Check passed buffer is legal user-space table name.
259    * @param b Table name.
260    * @return Returns passed <code>b</code> param
261    * @throws NullPointerException If passed <code>b</code> is null
262    * @throws IllegalArgumentException if passed a table name
263    * that is made of other than 'word' characters or underscores: i.e.
264    * <code>[a-zA-Z_0-9].
265    */
266   public static byte [] isLegalTableName(final byte [] b) {
267     if (b == null || b.length <= 0) {
268       throw new IllegalArgumentException("Name is null or empty");
269     }
270     if (b[0] == '.' || b[0] == '-') {
271       throw new IllegalArgumentException("Illegal first character <" + b[0] +
272           "> at 0. User-space table names can only start with 'word " +
273           "characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(b));
274     }
275     for (int i = 0; i < b.length; i++) {
276       if (Character.isLetterOrDigit(b[i]) || b[i] == '_' || b[i] == '-' ||
277           b[i] == '.') {
278         continue;
279       }
280       throw new IllegalArgumentException("Illegal character <" + b[i] +
281         "> at " + i + ". User-space table names can only contain " +
282         "'word characters': i.e. [a-zA-Z_0-9-.]: " + Bytes.toString(b));
283     }
284     return b;
285   }
286 
287   /**
288    * @param key The key.
289    * @return The value.
290    */
291   public byte[] getValue(byte[] key) {
292     return getValue(new ImmutableBytesWritable(key));
293   }
294 
295   private byte[] getValue(final ImmutableBytesWritable key) {
296     ImmutableBytesWritable ibw = values.get(key);
297     if (ibw == null)
298       return null;
299     return ibw.get();
300   }
301 
302   /**
303    * @param key The key.
304    * @return The value as a string.
305    */
306   public String getValue(String key) {
307     byte[] value = getValue(Bytes.toBytes(key));
308     if (value == null)
309       return null;
310     return Bytes.toString(value);
311   }
312 
313   /**
314    * @return All values.
315    */
316   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
317      return Collections.unmodifiableMap(values);
318   }
319 
320   /**
321    * @param key The key.
322    * @param value The value.
323    */
324   public void setValue(byte[] key, byte[] value) {
325     setValue(new ImmutableBytesWritable(key), value);
326   }
327 
328   /*
329    * @param key The key.
330    * @param value The value.
331    */
332   private void setValue(final ImmutableBytesWritable key,
333       final byte[] value) {
334     values.put(key, new ImmutableBytesWritable(value));
335   }
336 
337   /*
338    * @param key The key.
339    * @param value The value.
340    */
341   private void setValue(final ImmutableBytesWritable key,
342       final ImmutableBytesWritable value) {
343     values.put(key, value);
344   }
345 
346   /**
347    * @param key The key.
348    * @param value The value.
349    */
350   public void setValue(String key, String value) {
351     setValue(Bytes.toBytes(key), Bytes.toBytes(value));
352   }
353 
354   /**
355    * @param key Key whose key and value we're to remove from HTD parameters.
356    */
357   public void remove(final byte [] key) {
358     values.remove(new ImmutableBytesWritable(key));
359   }
360 
361   /**
362    * @return true if all columns in the table should be read only
363    */
364   public boolean isReadOnly() {
365     return isSomething(READONLY_KEY, DEFAULT_READONLY);
366   }
367 
368   /**
369    * @param readOnly True if all of the columns in the table should be read
370    * only.
371    */
372   public void setReadOnly(final boolean readOnly) {
373     setValue(READONLY_KEY, readOnly? TRUE: FALSE);
374   }
375 
376   /**
377    * @return true if that table's log is hflush by other means
378    */
379   public synchronized boolean isDeferredLogFlush() {
380     if(this.isDeferredLog == null) {
381       this.isDeferredLog =
382           isSomething(DEFERRED_LOG_FLUSH_KEY, DEFAULT_DEFERRED_LOG_FLUSH);
383     }
384     return this.isDeferredLog;
385   }
386 
387   /**
388    * @param isDeferredLogFlush true if that table's log is hlfush by oter means
389    * only.
390    */
391   public void setDeferredLogFlush(final boolean isDeferredLogFlush) {
392     setValue(DEFERRED_LOG_FLUSH_KEY, isDeferredLogFlush? TRUE: FALSE);
393   }
394 
395   /** @return name of table */
396   public byte [] getName() {
397     return name;
398   }
399 
400   /** @return name of table */
401   public String getNameAsString() {
402     return this.nameAsString;
403   }
404 
405   /** @return max hregion size for table */
406   public long getMaxFileSize() {
407     byte [] value = getValue(MAX_FILESIZE_KEY);
408     if (value != null)
409       return Long.valueOf(Bytes.toString(value)).longValue();
410     return HConstants.DEFAULT_MAX_FILE_SIZE;
411   }
412 
413   /** @param name name of table */
414   public void setName(byte[] name) {
415     this.name = name;
416   }
417 
418   /**
419    * @param maxFileSize The maximum file size that a store file can grow to
420    * before a split is triggered.
421    */
422   public void setMaxFileSize(long maxFileSize) {
423     setValue(MAX_FILESIZE_KEY, Bytes.toBytes(Long.toString(maxFileSize)));
424   }
425 
426   /**
427    * @return memory cache flush size for each hregion
428    */
429   public long getMemStoreFlushSize() {
430     byte [] value = getValue(MEMSTORE_FLUSHSIZE_KEY);
431     if (value != null)
432       return Long.valueOf(Bytes.toString(value)).longValue();
433     return DEFAULT_MEMSTORE_FLUSH_SIZE;
434   }
435 
436   /**
437    * @param memstoreFlushSize memory cache flush size for each hregion
438    */
439   public void setMemStoreFlushSize(long memstoreFlushSize) {
440     setValue(MEMSTORE_FLUSHSIZE_KEY,
441       Bytes.toBytes(Long.toString(memstoreFlushSize)));
442   }
443 
444   /**
445    * Adds a column family.
446    * @param family HColumnDescriptor of familyto add.
447    */
448   public void addFamily(final HColumnDescriptor family) {
449     if (family.getName() == null || family.getName().length <= 0) {
450       throw new NullPointerException("Family name cannot be null or empty");
451     }
452     this.families.put(family.getName(), family);
453   }
454 
455   /**
456    * Checks to see if this table contains the given column family
457    * @param c Family name or column name.
458    * @return true if the table contains the specified family name
459    */
460   public boolean hasFamily(final byte [] c) {
461     return families.containsKey(c);
462   }
463 
464   /**
465    * @return Name of this table and then a map of all of the column family
466    * descriptors.
467    * @see #getNameAsString()
468    */
469   @Override
470   public String toString() {
471     StringBuilder s = new StringBuilder();
472     s.append('{');
473     s.append(HConstants.NAME);
474     s.append(" => '");
475     s.append(Bytes.toString(name));
476     s.append("'");
477     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
478         values.entrySet()) {
479       String key = Bytes.toString(e.getKey().get());
480       String value = Bytes.toString(e.getValue().get());
481       if (key == null) {
482         continue;
483       }
484       String upperCase = key.toUpperCase();
485       if (upperCase.equals(IS_ROOT) || upperCase.equals(IS_META)) {
486         // Skip. Don't bother printing out read-only values if false.
487         if (value.toLowerCase().equals(Boolean.FALSE.toString())) {
488           continue;
489         }
490       }
491       s.append(", ");
492       s.append(Bytes.toString(e.getKey().get()));
493       s.append(" => '");
494       s.append(Bytes.toString(e.getValue().get()));
495       s.append("'");
496     }
497     s.append(", ");
498     s.append(FAMILIES);
499     s.append(" => ");
500     s.append(families.values());
501     s.append('}');
502     return s.toString();
503   }
504 
505   /**
506    * @see java.lang.Object#equals(java.lang.Object)
507    */
508   @Override
509   public boolean equals(Object obj) {
510     if (this == obj) {
511       return true;
512     }
513     if (obj == null) {
514       return false;
515     }
516     if (!(obj instanceof HTableDescriptor)) {
517       return false;
518     }
519     return compareTo((HTableDescriptor)obj) == 0;
520   }
521 
522   /**
523    * @see java.lang.Object#hashCode()
524    */
525   @Override
526   public int hashCode() {
527     int result = Bytes.hashCode(this.name);
528     result ^= Byte.valueOf(TABLE_DESCRIPTOR_VERSION).hashCode();
529     if (this.families != null && this.families.size() > 0) {
530       for (HColumnDescriptor e: this.families.values()) {
531         result ^= e.hashCode();
532       }
533     }
534     result ^= values.hashCode();
535     return result;
536   }
537 
538   // Writable
539 
540   public void readFields(DataInput in) throws IOException {
541     int version = in.readInt();
542     if (version < 3)
543       throw new IOException("versions < 3 are not supported (and never existed!?)");
544     // version 3+
545     name = Bytes.readByteArray(in);
546     nameAsString = Bytes.toString(this.name);
547     setRootRegion(in.readBoolean());
548     setMetaRegion(in.readBoolean());
549     values.clear();
550     int numVals = in.readInt();
551     for (int i = 0; i < numVals; i++) {
552       ImmutableBytesWritable key = new ImmutableBytesWritable();
553       ImmutableBytesWritable value = new ImmutableBytesWritable();
554       key.readFields(in);
555       value.readFields(in);
556       values.put(key, value);
557     }
558     families.clear();
559     int numFamilies = in.readInt();
560     for (int i = 0; i < numFamilies; i++) {
561       HColumnDescriptor c = new HColumnDescriptor();
562       c.readFields(in);
563       families.put(c.getName(), c);
564     }
565     if (version < 4) {
566       return;
567     }
568   }
569 
570   public void write(DataOutput out) throws IOException {
571 	out.writeInt(TABLE_DESCRIPTOR_VERSION);
572     Bytes.writeByteArray(out, name);
573     out.writeBoolean(isRootRegion());
574     out.writeBoolean(isMetaRegion());
575     out.writeInt(values.size());
576     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
577         values.entrySet()) {
578       e.getKey().write(out);
579       e.getValue().write(out);
580     }
581     out.writeInt(families.size());
582     for(Iterator<HColumnDescriptor> it = families.values().iterator();
583         it.hasNext(); ) {
584       HColumnDescriptor family = it.next();
585       family.write(out);
586     }
587   }
588 
589   // Comparable
590 
591   public int compareTo(final HTableDescriptor other) {
592     int result = Bytes.compareTo(this.name, other.name);
593     if (result == 0) {
594       result = families.size() - other.families.size();
595     }
596     if (result == 0 && families.size() != other.families.size()) {
597       result = Integer.valueOf(families.size()).compareTo(
598           Integer.valueOf(other.families.size()));
599     }
600     if (result == 0) {
601       for (Iterator<HColumnDescriptor> it = families.values().iterator(),
602           it2 = other.families.values().iterator(); it.hasNext(); ) {
603         result = it.next().compareTo(it2.next());
604         if (result != 0) {
605           break;
606         }
607       }
608     }
609     if (result == 0) {
610       // punt on comparison for ordering, just calculate difference
611       result = this.values.hashCode() - other.values.hashCode();
612       if (result < 0)
613         result = -1;
614       else if (result > 0)
615         result = 1;
616     }
617     return result;
618   }
619 
620   /**
621    * @return Immutable sorted map of families.
622    */
623   public Collection<HColumnDescriptor> getFamilies() {
624     return Collections.unmodifiableCollection(this.families.values());
625   }
626 
627   /**
628    * @return Immutable sorted set of the keys of the families.
629    */
630   public Set<byte[]> getFamiliesKeys() {
631     return Collections.unmodifiableSet(this.families.keySet());
632   }
633 
634   public HColumnDescriptor[] getColumnFamilies() {
635     return getFamilies().toArray(new HColumnDescriptor[0]);
636   }
637 
638   /**
639    * @param column
640    * @return Column descriptor for the passed family name or the family on
641    * passed in column.
642    */
643   public HColumnDescriptor getFamily(final byte [] column) {
644     return this.families.get(column);
645   }
646 
647   /**
648    * @param column
649    * @return Column descriptor for the passed family name or the family on
650    * passed in column.
651    */
652   public HColumnDescriptor removeFamily(final byte [] column) {
653     return this.families.remove(column);
654   }
655 
656   /**
657    * @param rootdir qualified path of HBase root directory
658    * @param tableName name of table
659    * @return path for table
660    */
661   public static Path getTableDir(Path rootdir, final byte [] tableName) {
662     return new Path(rootdir, Bytes.toString(tableName));
663   }
664 
665   /** Table descriptor for <core>-ROOT-</code> catalog table */
666   public static final HTableDescriptor ROOT_TABLEDESC = new HTableDescriptor(
667       HConstants.ROOT_TABLE_NAME,
668       new HColumnDescriptor[] { new HColumnDescriptor(HConstants.CATALOG_FAMILY,
669           10,  // Ten is arbitrary number.  Keep versions to help debuggging.
670           Compression.Algorithm.NONE.getName(), true, true, 8 * 1024,
671           HConstants.FOREVER, StoreFile.BloomType.NONE.toString(),  
672           HConstants.REPLICATION_SCOPE_LOCAL) });
673 
674   /** Table descriptor for <code>.META.</code> catalog table */
675   public static final HTableDescriptor META_TABLEDESC = new HTableDescriptor(
676       HConstants.META_TABLE_NAME, new HColumnDescriptor[] {
677           new HColumnDescriptor(HConstants.CATALOG_FAMILY,
678             10, // Ten is arbitrary number.  Keep versions to help debuggging.
679             Compression.Algorithm.NONE.getName(), true, true, 8 * 1024,
680             HConstants.FOREVER, StoreFile.BloomType.NONE.toString(),
681             HConstants.REPLICATION_SCOPE_LOCAL),
682           new HColumnDescriptor(HConstants.CATALOG_HISTORIAN_FAMILY,
683             HConstants.ALL_VERSIONS, Compression.Algorithm.NONE.getName(),
684             false, false,  8 * 1024,
685             HConstants.WEEK_IN_SECONDS,StoreFile.BloomType.NONE.toString(),
686             HConstants.REPLICATION_SCOPE_LOCAL)});
687 }